Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 28 additions & 4 deletions crates/cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,19 +79,43 @@ pub fn build_exec_router() -> anyhow::Result<Router> {

/// Open the vault from the default location (~/.relais/vault/).
///
/// Reads the master password from `RELAIS_VAULT_PASSWORD` or falls back to a
/// hard-coded development default.
/// The master password comes from `RELAIS_VAULT_PASSWORD`. There is no silent
/// insecure default: if it is unset, opening fails (callers that tolerate a missing
/// vault use `open_vault().ok()`), unless `RELAIS_ALLOW_DEV_VAULT_PASSWORD=1` enables
/// the old dev password for local development.
pub fn open_vault() -> anyhow::Result<relais_core::vault::Vault> {
let vault_dir = dirs::home_dir()
.ok_or_else(|| anyhow::anyhow!("could not find home directory"))?
.join(".relais")
.join("vault");
std::fs::create_dir_all(&vault_dir)?;
let password =
std::env::var("RELAIS_VAULT_PASSWORD").unwrap_or_else(|_| "relais-dev-password".into());
let password = vault_password()?;
Ok(relais_core::vault::Vault::open(&vault_dir, &password)?)
}

/// Resolve the vault master password, refusing a silent insecure default (H2).
fn vault_password() -> anyhow::Result<String> {
if let Ok(p) = std::env::var("RELAIS_VAULT_PASSWORD") {
if !p.is_empty() {
return Ok(p);
}
}
let allow_dev = matches!(
std::env::var("RELAIS_ALLOW_DEV_VAULT_PASSWORD").as_deref(),
Ok("1") | Ok("true")
);
if allow_dev {
tracing::warn!(
"RELAIS_ALLOW_DEV_VAULT_PASSWORD is set — using the insecure default vault password (DEV ONLY)"
);
return Ok("relais-dev-password".into());
}
anyhow::bail!(
"RELAIS_VAULT_PASSWORD is not set; refusing to open the vault with a default password. \
Set a strong RELAIS_VAULT_PASSWORD. For local dev only: RELAIS_ALLOW_DEV_VAULT_PASSWORD=1"
)
}

/// Build a Router with all built-in adapters registered.
pub fn build_router() -> Router {
let mut router = Router::new();
Expand Down
4 changes: 4 additions & 0 deletions crates/cli/src/commands/vault.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ pub fn run(action: &VaultAction) -> Result<()> {
vault.delete(site)?;
println!("Deleted credential for '{site}'");
}
VaultAction::Migrate => {
let n = vault.migrate()?;
println!("Re-encrypted {n} credential(s) into the current vault format.");
}
}

Ok(())
Expand Down
2 changes: 2 additions & 0 deletions crates/cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ pub enum VaultAction {
/// Site ID to delete
site: String,
},
/// Re-encrypt all credentials into the current (v1) vault format
Migrate,
}

#[derive(Subcommand)]
Expand Down
11 changes: 11 additions & 0 deletions crates/cli/tests/cli_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,17 @@ fn cli_parses_vault_store() {
}
}

#[test]
fn cli_parses_vault_migrate() {
let cli = Cli::parse_from(["relais", "vault", "migrate"]);
assert!(matches!(
cli.command,
Commands::Vault {
action: VaultAction::Migrate
}
));
}

#[test]
fn cli_parses_vault_list() {
let cli = Cli::parse_from(["relais", "vault", "list"]);
Expand Down
2 changes: 2 additions & 0 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ anyhow.workspace = true
reqwest.workspace = true
sled = "0.34"
aes-gcm = "0.10"
chacha20poly1305 = "0.10"
argon2 = "0.5"
sha2 = "0.10"
rand = "0.8"

Expand Down
Loading
Loading