From 7460c6b70a3fd5231ede826f828553b814f7f054 Mon Sep 17 00:00:00 2001 From: jdx <216188+jdx@users.noreply.github.com> Date: Mon, 16 Dec 2024 06:33:52 -0600 Subject: [PATCH] fix: added MISE_SOPS_ROPS setting --- e2e/secrets/test_secrets | 4 +++ settings.toml | 6 ++++ src/config/env_directive/file.rs | 4 +-- src/sops.rs | 60 +++++++++++++++++++++++--------- 4 files changed, 56 insertions(+), 18 deletions(-) diff --git a/e2e/secrets/test_secrets b/e2e/secrets/test_secrets index 2eb4b03c66..e29d6d2510 100644 --- a/e2e/secrets/test_secrets +++ b/e2e/secrets/test_secrets @@ -21,3 +21,7 @@ mise x -- sops encrypt -i --age "$age_pub" .env.yaml export MISE_SOPS_AGE_KEY= assert "mise set _.file=.env.yaml" assert_contains "mise env" "export SECRET=mysecret" + +# sops +mise settings set sops.rops 0 +assert_contains "mise env" "export SECRET=mysecret" diff --git a/settings.toml b/settings.toml index 40f45544dc..b8c06563f4 100644 --- a/settings.toml +++ b/settings.toml @@ -304,6 +304,12 @@ a particular feature you'd like to try. Also, if something isn't working right, try disabling it if you can. """ +[sops.rops] +env = "MISE_SOPS_ROPS" +type = "Bool" +default = true +description = "Use rops to decrypt sops files. Disable to shell out to `sops` which will slow down mise but sops may offer features not available in rops." + [fetch_remote_versions_cache] env = "MISE_FETCH_REMOTE_VERSIONS_CACHE" type = "Duration" diff --git a/src/config/env_directive/file.rs b/src/config/env_directive/file.rs index f81a1c0118..cb2542b424 100644 --- a/src/config/env_directive/file.rs +++ b/src/config/env_directive/file.rs @@ -57,7 +57,7 @@ impl EnvResults { if let Ok(raw) = file::read_to_string(p) { let mut f: Env = serde_json::from_str(&raw).wrap_err_with(errfn)?; if !f.sops.is_empty() { - let raw = sops::decrypt::<_, JsonFileFormat>(&raw, parse_template)?; + let raw = sops::decrypt::<_, JsonFileFormat>(&raw, parse_template, "json")?; f = serde_json::from_str(&raw).wrap_err_with(errfn)?; } f.env @@ -87,7 +87,7 @@ impl EnvResults { if let Ok(raw) = file::read_to_string(p) { let mut f: Env = serde_yaml::from_str(&raw).wrap_err_with(errfn)?; if !f.sops.is_empty() { - let raw = sops::decrypt::<_, YamlFileFormat>(&raw, parse_template)?; + let raw = sops::decrypt::<_, YamlFileFormat>(&raw, parse_template, "yaml")?; f = serde_yaml::from_str(&raw).wrap_err_with(errfn)?; } f.env diff --git a/src/sops.rs b/src/sops.rs index 520500c4a7..88ff30f70b 100644 --- a/src/sops.rs +++ b/src/sops.rs @@ -7,14 +7,16 @@ use rops::cryptography::hasher::SHA512; use rops::file::state::EncryptedFile; use rops::file::RopsFile; use std::env; +use std::sync::{Mutex, OnceLock}; -pub fn decrypt(input: &str, parse_template: PT) -> result::Result +pub fn decrypt(input: &str, parse_template: PT, format: &str) -> result::Result where PT: Fn(String) -> result::Result, F: rops::file::format::FileFormat, { - static ONCE: std::sync::Once = std::sync::Once::new(); - ONCE.call_once(|| { + static AGE_KEY: OnceLock> = OnceLock::new(); + static MUTEX: Mutex<()> = Mutex::new(()); + let age = AGE_KEY.get_or_init(|| { let p = SETTINGS .sops .age_key_file @@ -24,9 +26,14 @@ where Ok(p) => p, Err(e) => { warn!("failed to parse sops age key file: {}", e); - return; + return None; } }); + if let Some(age_key) = &SETTINGS.sops.age_key { + if !age_key.is_empty() { + return Some(age_key.clone()); + } + } if p.exists() { if let Ok(raw) = file::read_to_string(p) { let key = raw @@ -34,19 +41,40 @@ where .lines() .filter(|l| !l.starts_with('#')) .collect::(); - env::set_var("ROPS_AGE", key); - } - } - if let Some(age_key) = &SETTINGS.sops.age_key { - if !age_key.is_empty() { - env::set_var("ROPS_AGE", age_key); + if !key.trim().is_empty() { + return Some(key); + } } } + None }); - let f = input - .parse::, F>>() - .wrap_err("failed to parse sops file")?; - Ok(f.decrypt::() - .wrap_err("failed to decrypt sops file")? - .to_string()) + let _lock = MUTEX.lock().unwrap(); // prevent multiple threads from using the same age key + let age_env_key = if SETTINGS.sops.rops { + "ROPS_AGE" + } else { + "SOPS_AGE_KEY" + }; + let prev_age = env::var(age_env_key).ok(); + if let Some(age) = &age { + env::set_var(age_env_key, age.trim()); + } + let output = if SETTINGS.sops.rops { + input + .parse::, F>>() + .wrap_err("failed to parse sops file")? + .decrypt::() + .wrap_err("failed to decrypt sops file")? + .to_string() + } else { + // TODO: this won't work right if sops is coming from mise since tools won't have been loaded + // TODO: this obviously won't work on windows + cmd!("sops", "--input-type", format, "--output-type", format, "-d", "/dev/stdin").stdin_bytes(input.as_bytes()).read()? + }; + + if let Some(age) = prev_age { + env::set_var(age_env_key, age); + } else { + env::remove_var(age_env_key); + } + Ok(output) }