From 20c54350ee70d1a6fa833f594c5f2c7c7c9f383e Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Sun, 27 Jul 2025 10:32:07 +0800 Subject: [PATCH] feat: v1.9.5 --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/cmd_config.rs | 18 +++-- src/config.rs | 187 +++++++++++++++++++++++++++++++++++++++++----- src/consts.rs | 3 +- 5 files changed, 182 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ce9f77e..6a6d63a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1989,7 +1989,7 @@ dependencies = [ [[package]] name = "tiny-encrypt" -version = "1.9.4" +version = "1.9.5" dependencies = [ "aes-gcm-stream", "base64 0.22.1", diff --git a/Cargo.toml b/Cargo.toml index c5c7b10..2e3516a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tiny-encrypt" -version = "1.9.4" +version = "1.9.5" edition = "2021" license = "MIT" description = "A simple and tiny file encrypt tool" diff --git a/src/cmd_config.rs b/src/cmd_config.rs index 3da4e2c..fd56391 100644 --- a/src/cmd_config.rs +++ b/src/cmd_config.rs @@ -101,13 +101,15 @@ fn strip_field(kid: &str, max_len: usize) -> String { fn config_profiles(cmd_version: &CmdConfig, config: &TinyEncryptConfig) -> XResult<()> { let mut reverse_map = HashMap::new(); - for (p, v) in &config.profiles { - let mut v2 = v.clone(); - v2.sort(); - let vs = v2.join(","); - match reverse_map.get_mut(&vs) { - None => { reverse_map.insert(vs, vec![(p, v)]); } - Some(vec) => { vec.push((p, v)); } + if let Some(profiles) = &config.profiles { + for (p, v) in profiles { + let mut v2 = v.clone(); + v2.sort(); + let vs = v2.join(","); + match reverse_map.get_mut(&vs) { + None => { reverse_map.insert(vs, vec![(p, v)]); } + Some(vec) => { vec.push((p, v)); } + } } } @@ -153,4 +155,4 @@ fn config_profiles(cmd_version: &CmdConfig, config: &TinyEncryptConfig) -> XResu println!("{}", table); Ok(()) -} \ No newline at end of file +} diff --git a/src/config.rs b/src/config.rs index 499dab4..8d0fb76 100644 --- a/src/config.rs +++ b/src/config.rs @@ -8,7 +8,7 @@ use rust_util::util_file::resolve_file_path; use rust_util::{debugging, opt_result, simple_error, warning, XResult}; use serde::{Deserialize, Serialize}; -use crate::consts::{TINY_ENC_CONFIG_FILE, TINY_ENC_CONFIG_FILE_2, TINY_ENC_FILE_EXT}; +use crate::consts::{TINY_ENC_CONFIG_FILE, TINY_ENC_CONFIG_FILE_2, TINY_ENC_CONFIG_FILE_3, TINY_ENC_FILE_EXT}; use crate::spec::TinyEncryptEnvelopType; /// Config file sample: @@ -39,8 +39,18 @@ use crate::spec::TinyEncryptEnvelopType; pub struct TinyEncryptConfig { pub environment: Option>, pub namespaces: Option>, + pub includes: Option, // find all *.tinyencrypt.json pub envelops: Vec, - pub profiles: HashMap>, + pub profiles: Option>>, +} + +impl TinyEncryptConfig { + fn get_profile(&self, profile: &str) -> Option<&Vec> { + match &self.profiles { + Some(profiles) => profiles.get(profile), + None => None, + } + } } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -68,14 +78,18 @@ impl TinyEncryptConfig { pub fn load_default() -> XResult { let resolved_file = resolve_file_path(TINY_ENC_CONFIG_FILE); let resolved_file_2 = resolve_file_path(TINY_ENC_CONFIG_FILE_2); + let resolved_file_3 = resolve_file_path(TINY_ENC_CONFIG_FILE_3); let config_file = if fs::metadata(&resolved_file).is_ok() { debugging!("Load config from: {resolved_file}"); resolved_file } else if fs::metadata(&resolved_file_2).is_ok() { debugging!("Load config from: {resolved_file_2}"); resolved_file_2 + } else if fs::metadata(&resolved_file_3).is_ok() { + debugging!("Load config from: {resolved_file_3}"); + resolved_file_3 } else { - warning!("Cannot find config file from:\n- {resolved_file}\n- {resolved_file_2}"); + warning!("Cannot find config file from:\n- {resolved_file}\n- {resolved_file_2}\n- {resolved_file_3}"); resolved_file }; Self::load(&config_file) @@ -88,26 +102,29 @@ impl TinyEncryptConfig { "Read config file: {}, failed: {}", file ); - let mut config: TinyEncryptConfig = opt_result!( - // serde_json::from_str(&config_contents), - json5::from_str(&config_contents), + let config: TinyEncryptConfig = opt_result!( + serde_json::from_str(&config_contents), "Parse config file: {}, failed: {}", file ); - let mut splited_profiles = HashMap::new(); - for (k, v) in config.profiles.into_iter() { - if !k.contains(',') { - splited_profiles.insert(k, v); - } else { - k.split(',') - .map(|k| k.trim()) - .filter(|k| !k.is_empty()) - .for_each(|k| { - splited_profiles.insert(k.to_string(), v.clone()); - }); + let mut config = load_includes_and_merge(config); + + if let Some(profiles) = config.profiles { + let mut splited_profiles = HashMap::new(); + for (k, v) in profiles.into_iter() { + if !k.contains(',') { + splited_profiles.insert(k, v); + } else { + k.split(',') + .map(|k| k.trim()) + .filter(|k| !k.is_empty()) + .for_each(|k| { + splited_profiles.insert(k.to_string(), v.clone()); + }); + } } + config.profiles = Some(splited_profiles); } - config.profiles = splited_profiles; if let Some(environment) = &config.environment { for (k, v) in environment { @@ -221,7 +238,7 @@ impl TinyEncryptConfig { self.envelops.iter().for_each(|e| { key_ids.push(e.kid.to_string()); }); - } else if let Some(kids) = self.profiles.get(profile) { + } else if let Some(kids) = self.get_profile(profile) { kids.iter().for_each(|k| key_ids.push(k.to_string())); } } @@ -275,3 +292,135 @@ pub fn resolve_path_namespace( Some(config) => config.resolve_path_namespace(path, append_te), } } + +pub fn load_includes_and_merge(mut config: TinyEncryptConfig) -> TinyEncryptConfig { + if let Some(includes) = &config.includes { + let sub_configs = search_include_configs(&includes); + debugging!( + "Found {} sub configs, detail {:?}", + sub_configs.len(), + sub_configs + ); + for sub_config in &sub_configs { + // merge environment + if let Some(sub_environment) = &sub_config.environment { + match &mut config.environment { + None => { + config.environment = Some(sub_environment.clone()); + } + Some(env) => { + for (k, v) in sub_environment { + match env.get_mut(k) { + None => { + env.insert(k.clone(), v.clone()); + } + Some(env_val) => { + match (env_val, v) { + (StringOrVecString::Vec(env_value_vec), StringOrVecString::Vec(v_vec)) => { + for vv in v_vec { + if !env_value_vec.contains(vv) { + env_value_vec.push(vv.clone()); + } + } + } + _ => { + warning!("Duplicate or mis-match environment value, key: {}", k); + } + } + } + } + } + } + } + } + // merge profiles + for sub_envelop in &sub_config.envelops { + let filter_envelops = config.envelops.iter().filter(|e| { + e.kid == sub_envelop.kid || (e.sid.is_some() && e.sid == sub_envelop.sid) + }).collect::>(); + if !filter_envelops.is_empty() { + warning!("Duplication kid: {} or sid: {:?}", sub_envelop.kid, sub_envelop.sid); + continue; + } + config.envelops.push(sub_envelop.clone()); + } + // merge profiles + if let Some(sub_profiles) = &sub_config.profiles { + match &mut config.profiles { + None => { + config.profiles = Some(sub_profiles.clone()); + } + Some(profiles) => { + for (k, v) in sub_profiles { + match profiles.get_mut(k) { + None => { + profiles.insert(k.clone(), v.clone()); + } + Some(env_val) => { + for vv in v { + env_val.push(vv.clone()); + } + } + } + } + } + } + } + if let Some(profiles) = &mut config.profiles { + let all_key_ids = config.envelops.iter().map(|e| e.kid.clone()).collect::>(); + if profiles.contains_key("__all__") { + warning!("Key __all__ in profiles exists") + } else { + profiles.insert("__all__".to_string(), all_key_ids); + } + } + } + } + config +} + +pub fn search_include_configs(includes_path: &str) -> Vec { + let mut sub_configs = vec![]; + let read_dir = match fs::read_dir(includes_path) { + Ok(read_dir) => read_dir, + Err(e) => { + warning!("Read dir: {}, failed: {}", includes_path, e); + return sub_configs; + } + }; + for entry in read_dir { + let entry = match entry { + Ok(entry) => entry, + Err(e) => { + warning!("Read dir: {} entry, failed: {}", includes_path, e); + continue; + } + }; + let file_name = entry.file_name(); + let file_name = file_name.to_str(); + let file_name = match file_name { + Some(file_name) => file_name, + None => continue, + }; + if file_name.ends_with(".tinyencrypt.json") { + debugging!("Matches config file: {}", file_name); + let file_path = entry.path(); + let content = match fs::read_to_string(entry.path()) { + Ok(content) => content, + Err(e) => { + warning!("Read config file: {:?}, failed: {}", file_path, e); + continue; + } + }; + let config = match serde_json::from_str::(&content) { + Ok(config) => config, + Err(e) => { + warning!("Parse config file: {:?}, failed: {}", file_path, e); + continue; + } + }; + sub_configs.push(config); + } + } + sub_configs +} diff --git a/src/consts.rs b/src/consts.rs index 71f521e..8c16e5c 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -12,7 +12,8 @@ pub const ENC_CHACHA20_POLY1305_KYBER1204: &str = "chacha20-poly1305-kyber1204"; pub const TINY_ENC_FILE_EXT: &str = ".tinyenc"; pub const TINY_ENC_PEM_FILE_EXT: &str = ".tinyenc.pem"; pub const TINY_ENC_CONFIG_FILE: &str = "~/.tinyencrypt/config-rs.json"; -pub const TINY_ENC_CONFIG_FILE_2: &str = "/etc/tinyencrypt/config-rs.json"; +pub const TINY_ENC_CONFIG_FILE_2: &str = "~/.config/tinyencrypt-rs.json"; +pub const TINY_ENC_CONFIG_FILE_3: &str = "/etc/tinyencrypt/config-rs.json"; pub const TINY_ENC_PEM_NAME: &str = "TINY ENCRYPT";