mirror of
https://github.com/koverstreet/bcachefs-tools.git
synced 2025-12-10 00:00:24 +03:00
feat(mount): make unlock policy optional/explict
This changes the semantics of some arguments related to unlocking and slightly changes the unlocking logic. Also update help formatting/text. Instead of defaulting to `UnlockPolicy::Ask`, the argument becomes optional. That means if it is specified, the user really wants that specific policy. Similar to how `passphrase_file` also works. This also extends `UnlockPolicy` to override `isatty` detection. Fixes: #292 Signed-off-by: Thomas Mühlbacher <tmuehlbacher@posteo.net>
This commit is contained in:
parent
a411e7237f
commit
9bd3ada1d1
@ -215,28 +215,20 @@ fn get_uuid_for_dev_node(
|
|||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
pub struct Cli {
|
pub struct Cli {
|
||||||
/// Path to passphrase/key file
|
/// Path to passphrase file
|
||||||
///
|
///
|
||||||
/// Precedes key_location/unlock_policy: if the filesystem can be decrypted
|
/// This can be used to optionally specify a file to read the passphrase
|
||||||
/// by the specified passphrase file; it is decrypted. (i.e. Regardless
|
/// from. An explictly specified key_location/unlock_policy overrides this
|
||||||
/// if "fail" is specified for key_location/unlock_policy.)
|
/// argument.
|
||||||
#[arg(short = 'f', long)]
|
#[arg(short = 'f', long)]
|
||||||
passphrase_file: Option<PathBuf>,
|
passphrase_file: Option<PathBuf>,
|
||||||
|
|
||||||
/// Password policy to use in case of encrypted filesystem.
|
/// Passphrase policy to use in case of an encrypted filesystem. If not
|
||||||
///
|
/// specified, the password will be searched for in the keyring. If not
|
||||||
/// Possible values are:
|
/// found, the password will be prompted or read from stdin, depending on
|
||||||
/// "fail" - don't ask for password, fail if filesystem is encrypted;
|
/// whether the stdin is connected to a terminal or not.
|
||||||
/// "wait" - wait for password to become available before mounting;
|
#[arg(short = 'k', long = "key_location", value_enum)]
|
||||||
/// "ask" - prompt the user for password;
|
unlock_policy: Option<UnlockPolicy>,
|
||||||
#[arg(
|
|
||||||
short = 'k',
|
|
||||||
long = "key_location",
|
|
||||||
value_enum,
|
|
||||||
default_value_t,
|
|
||||||
verbatim_doc_comment
|
|
||||||
)]
|
|
||||||
unlock_policy: UnlockPolicy,
|
|
||||||
|
|
||||||
/// Device, or UUID=\<UUID\>
|
/// Device, or UUID=\<UUID\>
|
||||||
dev: String,
|
dev: String,
|
||||||
@ -305,7 +297,23 @@ fn devs_str_sbs_from_device(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cmd_mount_inner(cli: Cli) -> Result<()> {
|
/// If a user explicitly specifies `unlock_policy` or `passphrase_file` then use
|
||||||
|
/// that without falling back to other mechanisms. If these options are not
|
||||||
|
/// used, then search for the key or ask for it.
|
||||||
|
fn handle_unlock(cli: &Cli, sb: &bch_sb_handle) -> Result<KeyHandle> {
|
||||||
|
if let Some(policy) = cli.unlock_policy.as_ref() {
|
||||||
|
return policy.apply(sb);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(path) = cli.passphrase_file.as_deref() {
|
||||||
|
return Passphrase::new_from_file(path).and_then(|p| KeyHandle::new(sb, &p));
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyHandle::new_from_search(&sb.sb().uuid())
|
||||||
|
.or_else(|_| Passphrase::new().and_then(|p| KeyHandle::new(sb, &p)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd_mount_inner(cli: &Cli) -> Result<()> {
|
||||||
// Grab the udev information once
|
// Grab the udev information once
|
||||||
let udev_info = udev_bcachefs_info()?;
|
let udev_info = udev_bcachefs_info()?;
|
||||||
|
|
||||||
@ -325,7 +333,7 @@ fn cmd_mount_inner(cli: Cli) -> Result<()> {
|
|||||||
.map(read_super_silent)
|
.map(read_super_silent)
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
(cli.dev, sbs)
|
(cli.dev.clone(), sbs)
|
||||||
} else {
|
} else {
|
||||||
devs_str_sbs_from_device(&udev_info, Path::new(&cli.dev))?
|
devs_str_sbs_from_device(&udev_info, Path::new(&cli.dev))?
|
||||||
}
|
}
|
||||||
@ -334,26 +342,11 @@ fn cmd_mount_inner(cli: Cli) -> Result<()> {
|
|||||||
ensure!(!sbs.is_empty(), "No device(s) to mount specified");
|
ensure!(!sbs.is_empty(), "No device(s) to mount specified");
|
||||||
|
|
||||||
let first_sb = sbs[0];
|
let first_sb = sbs[0];
|
||||||
let uuid = first_sb.sb().uuid();
|
|
||||||
|
|
||||||
if unsafe { bcachefs::bch2_sb_is_encrypted(first_sb.sb) } {
|
if unsafe { bcachefs::bch2_sb_is_encrypted(first_sb.sb) } {
|
||||||
let _key_handle: KeyHandle = KeyHandle::new_from_search(&uuid).or_else(|_| {
|
handle_unlock(cli, &first_sb)?;
|
||||||
cli.passphrase_file
|
|
||||||
.and_then(|path| match Passphrase::new_from_file(path) {
|
|
||||||
Ok(p) => Some(KeyHandle::new(&first_sb, &p)),
|
|
||||||
Err(e) => {
|
|
||||||
error!(
|
|
||||||
"Failed to read passphrase from file, falling back to prompt: {}",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| cli.unlock_policy.apply(&first_sb))
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(mountpoint) = cli.mountpoint {
|
if let Some(mountpoint) = cli.mountpoint.as_deref() {
|
||||||
info!(
|
info!(
|
||||||
"mounting with params: device: {}, target: {}, options: {}",
|
"mounting with params: device: {}, target: {}, options: {}",
|
||||||
devices,
|
devices,
|
||||||
@ -391,7 +384,7 @@ pub fn mount(mut argv: Vec<String>, symlink_cmd: Option<&str>) -> i32 {
|
|||||||
});
|
});
|
||||||
|
|
||||||
colored::control::set_override(cli.colorize);
|
colored::control::set_override(cli.colorize);
|
||||||
if let Err(e) = cmd_mount_inner(cli) {
|
if let Err(e) = cmd_mount_inner(&cli) {
|
||||||
error!("Fatal error: {}", e);
|
error!("Fatal error: {}", e);
|
||||||
1
|
1
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
35
src/key.rs
35
src/key.rs
@ -25,9 +25,14 @@ const BCH_KEY_MAGIC: &str = "bch**key";
|
|||||||
|
|
||||||
#[derive(Clone, Debug, clap::ValueEnum, strum::Display)]
|
#[derive(Clone, Debug, clap::ValueEnum, strum::Display)]
|
||||||
pub enum UnlockPolicy {
|
pub enum UnlockPolicy {
|
||||||
|
/// Don't ask for passphrase, fail if filesystem is encrypted
|
||||||
Fail,
|
Fail,
|
||||||
|
/// Wait for passphrase to become available before mounting
|
||||||
Wait,
|
Wait,
|
||||||
|
/// Interactively prompt the user for a passphrase
|
||||||
Ask,
|
Ask,
|
||||||
|
/// Try to read the passphrase from `stdin` without prompting
|
||||||
|
Stdin,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnlockPolicy {
|
impl UnlockPolicy {
|
||||||
@ -40,6 +45,7 @@ impl UnlockPolicy {
|
|||||||
Self::Fail => Err(anyhow!("no passphrase available")),
|
Self::Fail => Err(anyhow!("no passphrase available")),
|
||||||
Self::Wait => Ok(KeyHandle::wait_for_unlock(&uuid)?),
|
Self::Wait => Ok(KeyHandle::wait_for_unlock(&uuid)?),
|
||||||
Self::Ask => Passphrase::new_from_prompt().and_then(|p| KeyHandle::new(sb, &p)),
|
Self::Ask => Passphrase::new_from_prompt().and_then(|p| KeyHandle::new(sb, &p)),
|
||||||
|
Self::Stdin => Passphrase::new_from_stdin().and_then(|p| KeyHandle::new(sb, &p)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -154,20 +160,31 @@ impl Passphrase {
|
|||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
// blocks indefinitely if no input is available on stdin
|
pub fn new() -> Result<Self> {
|
||||||
fn new_from_prompt() -> Result<Self> {
|
if stdin().is_terminal() {
|
||||||
let passphrase = if stdin().is_terminal() {
|
Self::new_from_prompt()
|
||||||
Zeroizing::new(rpassword::prompt_password("Enter passphrase: ")?)
|
|
||||||
} else {
|
} else {
|
||||||
info!("Trying to read passphrase from stdin...");
|
Self::new_from_stdin()
|
||||||
let mut line = Zeroizing::new(String::new());
|
}
|
||||||
stdin().read_line(&mut line)?;
|
}
|
||||||
line
|
|
||||||
};
|
// blocks indefinitely if no input is available on stdin
|
||||||
|
pub fn new_from_prompt() -> Result<Self> {
|
||||||
|
let passphrase = Zeroizing::new(rpassword::prompt_password("Enter passphrase: ")?);
|
||||||
|
|
||||||
Ok(Self(CString::new(passphrase.trim_end_matches('\n'))?))
|
Ok(Self(CString::new(passphrase.trim_end_matches('\n'))?))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// blocks indefinitely if no input is available on stdin
|
||||||
|
pub fn new_from_stdin() -> Result<Self> {
|
||||||
|
info!("Trying to read passphrase from stdin...");
|
||||||
|
|
||||||
|
let mut line = Zeroizing::new(String::new());
|
||||||
|
stdin().read_line(&mut line)?;
|
||||||
|
|
||||||
|
Ok(Self(CString::new(line.trim_end_matches('\n'))?))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new_from_file(passphrase_file: impl AsRef<Path>) -> Result<Self> {
|
pub fn new_from_file(passphrase_file: impl AsRef<Path>) -> Result<Self> {
|
||||||
let passphrase_file = passphrase_file.as_ref();
|
let passphrase_file = passphrase_file.as_ref();
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user