diff --git a/README.md b/README.md index 041200e..b8d3e15 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ This tool should be ran from an exiting Arch Linux installations. It depends on * Arch install scripts * mkfs.fat * mkfs.btrfs +* *Optional*: cryptsetup Dependencies will be handled for you if you install alma from AUR. @@ -42,6 +43,7 @@ sudo alma chroot /dev/disk/by-id/usb-Generic_USB_Flash_Disk-0:0 ### Flags * `-p / --extra-packages` - Specify extra packages to install. For example: `-p htop tmux` * `-i / --interactive` - Drop you into interactive chroot to perform further customization +* `-e / --encrypted-root` - Encrypt the root filesystem. ## What exactly does it do? diff --git a/src/alma.rs b/src/alma.rs index b404eba..d30eb19 100644 --- a/src/alma.rs +++ b/src/alma.rs @@ -1,31 +1,39 @@ use super::block::BlockDevice; +use super::cryptsetup::EncryptedDevice; use super::error::{Error, ErrorKind}; use super::mountstack::{Filesystem, MountStack}; use failure::ResultExt; -use log::info; +use log::{debug, info}; use std::fs; use std::path::{Path, PathBuf}; -pub struct ALMA { +pub struct ALMA<'a> { block: BlockDevice, + encrypted_root: Option>, } -impl ALMA { - pub fn new(block: BlockDevice) -> Self { - Self { block } +impl<'a> ALMA<'a> { + pub fn new(block: BlockDevice, encrypted_root: Option>) -> Self { + Self { + block, + encrypted_root, + } } - pub fn mount<'a>(&self, path: &'a Path) -> Result, Error> { + pub fn mount<'b>(&self, path: &'b Path) -> Result, Error> { let mut mount_stack = MountStack::new(); + let root_device = if let Some(encrypted_root) = &self.encrypted_root { + PathBuf::from(encrypted_root.path()) + } else { + self.block.partition_device_path(3)? + }; + debug!("Root partition: {}", root_device.display()); + info!("Mounting filesystems to {}", path.display()); mount_stack - .mount( - &PathBuf::from(&self.block.partition_device_path(3)?), - path, - Filesystem::Btrfs, - None, - ).context(ErrorKind::Mounting)?; + .mount(&root_device, path, Filesystem::Btrfs, None) + .context(ErrorKind::Mounting)?; let boot_point = path.join("boot"); if !boot_point.exists() { diff --git a/src/cryptsetup.rs b/src/cryptsetup.rs new file mode 100644 index 0000000..e13ed8c --- /dev/null +++ b/src/cryptsetup.rs @@ -0,0 +1,68 @@ +use super::error::{Error, ErrorKind}; +use super::process::CommandExt; +use super::tool::Tool; +use log::{debug, warn}; +use std::path::{Path, PathBuf}; + +pub struct EncryptedDevice<'a> { + cryptsetup: &'a Tool, + name: &'a str, + path: PathBuf, +} + +impl<'a> EncryptedDevice<'a> { + pub fn prepare(cryptsetup: &Tool, device: &Path) -> Result<(), Error> { + debug!("Preparing encrypted device in {}", device.display()); + cryptsetup + .execute() + .arg("luksFormat") + .arg("-q") + .arg(device) + .run(ErrorKind::LuksSetup)?; + + Ok(()) + } + + pub fn open( + cryptsetup: &'a Tool, + device: &Path, + name: &'a str, + ) -> Result, Error> { + debug!("Opening encrypted device {} as {}", device.display(), name); + cryptsetup + .execute() + .arg("open") + .arg(device) + .arg(name) + .run(ErrorKind::LuksOpen)?; + + Ok(Self { + cryptsetup, + name, + path: PathBuf::from("/dev/mapper").join(name), + }) + } + + fn _close(&mut self) -> Result<(), Error> { + debug!("Closing encrypted device {}", self.name); + self.cryptsetup + .execute() + .arg("close") + .arg(self.name) + .run(ErrorKind::LuksClose)?; + + Ok(()) + } + + pub fn path(&self) -> &Path { + &self.path + } +} + +impl<'a> Drop for EncryptedDevice<'a> { + fn drop(&mut self) { + if self._close().is_err() { + warn!("Error closing {}", self.name); + } + } +} diff --git a/src/error.rs b/src/error.rs index 9ef9a78..3588222 100644 --- a/src/error.rs +++ b/src/error.rs @@ -58,6 +58,15 @@ pub enum ErrorKind { #[fail(display = "Failed umounting filesystems")] UmountFailure, + + #[fail(display = "Error setting up an encrypted device")] + LuksSetup, + + #[fail(display = "Error opening the encrypted device")] + LuksOpen, + + #[fail(display = "Error closing the encrypted device")] + LuksClose, } impl Fail for Error { diff --git a/src/main.rs b/src/main.rs index b3f9877..850531e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,18 +9,21 @@ use nix::sys::signal; mod alma; mod block; +mod cryptsetup; mod error; mod mountstack; mod process; mod tool; use alma::ALMA; +use cryptsetup::EncryptedDevice; use error::*; use failure::{Fail, ResultExt}; use log::{debug, error, info, warn}; use process::CommandExt; use simplelog::*; use std::fs; +use std::io::Write; use std::path::PathBuf; use std::process::exit; use std::thread; @@ -32,7 +35,7 @@ use tool::Tool; static MKINITCPIO: &'static str = "MODULES=() BINARIES=() FILES=() -HOOKS=(base udev block filesystems keyboard fsck)"; +HOOKS=(base udev keyboard consolefont block encrypt filesystems keyboard fsck)"; static JOURNALD_CONF: &'static str = " [Journal] @@ -43,7 +46,8 @@ SystemMaxUse=16M #[derive(StructOpt)] #[structopt(name = "alma", about = "Arch Linux Mobile Appliance")] struct App { - #[structopt(short = "v", long = "verbose", help = "Verbose output")] + /// Verbose output + #[structopt(short = "v", long = "verbose")] verbose: bool, #[structopt(subcommand)] @@ -72,6 +76,10 @@ struct CreateCommand { /// Enter interactive chroot before unmounting the drive #[structopt(short = "i", long = "interactive")] interactive: bool, + + /// Encrypt the root partition + #[structopt(short = "e", long = "encrypted-root")] + encrypted_root: bool, } #[derive(StructOpt)] @@ -79,6 +87,10 @@ struct ChrootCommand { /// Path starting with /dev/disk/by-id for the USB drive #[structopt(parse(from_os_str),)] block_device: PathBuf, + + /// Open an encrypted root partition + #[structopt(short = "e", long = "encrypted-root")] + encrypted_root: bool, } fn fix_fstab(fstab: &str) -> String { @@ -96,6 +108,16 @@ fn create(command: CreateCommand) -> Result<(), Error> { let genfstab = Tool::find("genfstab")?; let mkfat = Tool::find("mkfs.fat")?; let mkbtrfs = Tool::find("mkfs.btrfs")?; + let cryptsetup = if command.encrypted_root { + Some(Tool::find("cryptsetup")?) + } else { + None + }; + let blkid = if command.encrypted_root { + Some(Tool::find("blkid")?) + } else { + None + }; let block_device = block::BlockDevice::from_path(command.block_device)?; @@ -130,14 +152,28 @@ fn create(command: CreateCommand) -> Result<(), Error> { .run(ErrorKind::Formatting)?; let root_partition = block_device.partition_device_path(3)?; + let encrypted_root = if let Some(cryptsetup) = &cryptsetup { + EncryptedDevice::prepare(&cryptsetup, &root_partition)?; + Some(EncryptedDevice::open( + cryptsetup, + &root_partition, + "alma_root", + )?) + } else { + None + }; + mkbtrfs .execute() .arg("-f") - .arg(&root_partition) - .run(ErrorKind::Formatting)?; + .arg(if let Some(device) = &encrypted_root { + device.path() + } else { + &root_partition + }).run(ErrorKind::Formatting)?; - let alma = ALMA::new(block_device); - let mut mount_stack = alma.mount(mount_point.path())?; + let alma = ALMA::new(block_device, encrypted_root); + let mount_stack = alma.mount(mount_point.path())?; info!("Bootstrapping system"); pacstrap @@ -186,6 +222,30 @@ fn create(command: CreateCommand) -> Result<(), Error> { .args(&["mkinitcpio", "-p", "linux"]) .run(ErrorKind::Initramfs)?; + if cryptsetup.is_some() { + debug!("Setting up GRUB for an encrypted root partition"); + + let uuid = blkid + .unwrap() + .execute() + .arg(root_partition) + .args(&["-o", "value", "-s", "UUID"]) + .run_text_output(ErrorKind::Partitioning)?; + let trimmed = uuid.trim(); + debug!("Root partition UUID: {}", trimmed); + + let mut grub_file = fs::OpenOptions::new() + .append(true) + .open(mount_point.path().join("etc/default/grub")) + .context(ErrorKind::Bootloader)?; + + write!( + &mut grub_file, + "GRUB_CMDLINE_LINUX=\"cryptdevice=UUID={}:luks_root\"", + trimmed + ).context(ErrorKind::Bootloader)?; + } + info!("Installing the Bootloader"); arch_chroot .execute() @@ -205,18 +265,33 @@ fn create(command: CreateCommand) -> Result<(), Error> { info!("Unmounting filesystems"); mount_stack.umount()?; - info!("Installation succeeded. It is now safe to unplug your device."); - Ok(()) } fn chroot(command: ChrootCommand) -> Result<(), Error> { let arch_chroot = Tool::find("arch-chroot")?; + let cryptsetup = if command.encrypted_root { + Some(Tool::find("cryptsetup")?) + } else { + None + }; + let block_device = block::BlockDevice::from_path(command.block_device)?; let mount_point = tempdir().context(ErrorKind::TmpDirError)?; - let alma = ALMA::new(block_device); - let mut mount_stack = alma.mount(mount_point.path())?; + let root_partition = block_device.partition_device_path(3)?; + let encrypted_root = if let Some(cryptsetup) = &cryptsetup { + Some(EncryptedDevice::open( + cryptsetup, + &root_partition, + "alma_root", + )?) + } else { + None + }; + + let alma = ALMA::new(block_device, encrypted_root); + let mount_stack = alma.mount(mount_point.path())?; arch_chroot .execute()