lix-installer/src/action/darwin/encrypt_apfs_volume.rs
Ana Hobden c4274c93fb
Add action & Tune Tracing (#119)
* Add action

* Checkout so we have actions.yml

* yaml poking

* Handle GITHUB_TOKEN

* Don't ask github to do templating, use directives for logging

* Missing changes

* Fix build error

* Fix yaml even more

* Add shell command

* Add a wait on the socket again

* Print some debugging

* Use more correct env vars

* Correct install url logic

* Use different style for inputs

* Fix yaml errror

* Tweak around local-root

* provision nix-install.sh as well

* Use nix-install.sh path directory in NIX_INSTALL_URL

* Tweak variables to hopefully work

* Call it BINARY_ROOT instead

* Add exec output

* Set no-confirm

* no echo

* Add token to workflow

* Set no-confirm properly

* Add no-confirm back for uninstall

* Correct some env and vars

* CreateDirectory respects existing symlink

* Add a few more checks to the CI

* pass valid yaml...

* Slightly more aggressive cleanup of /nix

* Ensure steam-deck cleans /home/nix

* Add steam-deck check for persistence

* Canonicalize steam-deck persistence

* Ensure absolute path

* Inverted logic sad

* python3 on mac

* Add readme info and fix a extra-conf mistype

* Add unsaved changes

* More fine grained trace logging

* Restore spans we lost in refactor

* BuiltinPlanner can accept settings

* Reflect feedback

* Push actually working code hopefully this time

* Speeling
2022-12-16 18:55:28 +00:00

187 lines
5.4 KiB
Rust

use crate::{
action::{
darwin::NIX_VOLUME_MOUNTD_DEST, Action, ActionDescription, ActionError, StatefulAction,
},
execute_command,
};
use rand::Rng;
use std::path::{Path, PathBuf};
use tokio::process::Command;
use tracing::{span, Span};
/**
Encrypt an APFS volume
*/
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct EncryptApfsVolume {
disk: PathBuf,
name: String,
}
impl EncryptApfsVolume {
#[tracing::instrument(level = "debug", skip_all)]
pub async fn plan(
disk: impl AsRef<Path>,
name: impl AsRef<str>,
) -> Result<StatefulAction<Self>, ActionError> {
let name = name.as_ref().to_owned();
Ok(Self {
name,
disk: disk.as_ref().to_path_buf(),
}
.into())
}
}
#[async_trait::async_trait]
#[typetag::serde(name = "encrypt_volume")]
impl Action for EncryptApfsVolume {
fn tracing_synopsis(&self) -> String {
format!(
"Encrypt volume `{}` on disk `{}`",
self.name,
self.disk.display()
)
}
fn tracing_span(&self) -> Span {
span!(
tracing::Level::DEBUG,
"encrypt_volume",
disk = tracing::field::display(self.disk.display()),
)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(level = "debug", skip_all, fields(
disk = %self.disk.display(),
))]
async fn execute(&mut self) -> Result<(), ActionError> {
let Self { disk, name } = self;
// Generate a random password.
let password: String = {
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
abcdefghijklmnopqrstuvwxyz\
0123456789)(*&^%$#@!~";
const PASSWORD_LEN: usize = 32;
let mut rng = rand::thread_rng();
(0..PASSWORD_LEN)
.map(|_| {
let idx = rng.gen_range(0..CHARSET.len());
CHARSET[idx] as char
})
.collect()
};
let disk_str = disk.to_str().expect("Could not turn disk into string"); /* Should not reasonably ever fail */
execute_command(Command::new("/usr/sbin/diskutil").arg("mount").arg(&name))
.await
.map_err(ActionError::Command)?;
// Add the password to the user keychain so they can unlock it later.
execute_command(
Command::new("/usr/bin/security").process_group(0).args([
"add-generic-password",
"-a",
name.as_str(),
"-s",
name.as_str(),
"-l",
format!("{} encryption password", disk_str).as_str(),
"-D",
"Encrypted volume password",
"-j",
format!(
"Added automatically by the Nix installer for use by {NIX_VOLUME_MOUNTD_DEST}"
)
.as_str(),
"-w",
password.as_str(),
"-T",
"/System/Library/CoreServices/APFSUserAgent",
"-T",
"/System/Library/CoreServices/CSUserAgent",
"-T",
"/usr/bin/security",
"/Library/Keychains/System.keychain",
]),
)
.await
.map_err(ActionError::Command)?;
// Encrypt the mounted volume
execute_command(Command::new("/usr/sbin/diskutil").process_group(0).args([
"apfs",
"encryptVolume",
name.as_str(),
"-user",
"disk",
"-passphrase",
password.as_str(),
]))
.await
.map_err(ActionError::Command)?;
execute_command(
Command::new("/usr/sbin/diskutil")
.process_group(0)
.arg("unmount")
.arg("force")
.arg(&name),
)
.await
.map_err(ActionError::Command)?;
Ok(())
}
fn revert_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
format!(
"Remove encryption keys for volume `{}`",
self.disk.display()
),
vec![],
)]
}
#[tracing::instrument(level = "debug", skip_all, fields(
disk = %self.disk.display(),
))]
async fn revert(&mut self) -> Result<(), ActionError> {
let Self { disk, name } = self;
let disk_str = disk.to_str().expect("Could not turn disk into string"); /* Should not reasonably ever fail */
// TODO: This seems very rough and unsafe
execute_command(
Command::new("/usr/bin/security").process_group(0).args([
"delete-generic-password",
"-a",
name.as_str(),
"-s",
name.as_str(),
"-l",
format!("{} encryption password", disk_str).as_str(),
"-D",
"Encrypted volume password",
"-j",
format!(
"Added automatically by the Nix installer for use by {NIX_VOLUME_MOUNTD_DEST}"
)
.as_str(),
]),
)
.await
.map_err(ActionError::Command)?;
Ok(())
}
}