Skip to content

Commit 80598e4

Browse files
author
Alexander Weber
committed
fmt and stuff
1 parent 9de1530 commit 80598e4

File tree

9 files changed

+275
-113
lines changed

9 files changed

+275
-113
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ clap_complete = "4.5.57"
2929
clap_mangen = "0.2.29"
3030
clap-markdown = "0.1.5"
3131
serde = { version = "1.0.219", features = ["derive"] }
32+
serde_json = "1.0.145"
3233
hocon = "0.9"
3334
anyhow = "1.0.99"
3435
path-clean = "1.0.1"

crates/secenv/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ clap_complete = { workspace = true }
2121
clap_mangen = { workspace = true }
2222
clap-markdown = { workspace = true }
2323
serde = { workspace = true }
24+
serde_json = { workspace = true }
2425
hocon = { workspace = true }
2526
anyhow = { workspace = true }
2627
path-clean = { workspace = true }

crates/secenv/src/args.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,6 @@ impl ClapArgumentLoader {
204204

205205
let profile_name = subc.get_one::<String>("profile").unwrap();
206206

207-
// Validate that the profile exists
208207
if !cfg.profiles.contains_key(profile_name) {
209208
return Err(anyhow::anyhow!("Profile '{}' not found in config", profile_name));
210209
}

crates/secenv/src/gpg.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,7 @@ impl GpgManager {
2121
Ok(Self)
2222
}
2323

24-
/// Export a GPG private key by fingerprint using the gpg CLI
2524
pub fn export_private_key(&self, spec: &GpgKeySpec) -> Result<String> {
26-
// Use gpg --export-secret-keys to get the private key in ASCII armor format
27-
// Try different export options for better Sequoia OpenPGP compatibility
2825
let mut cmd = Command::new("gpg");
2926
cmd.args([
3027
"--export-secret-keys",
@@ -61,7 +58,6 @@ impl GpgManager {
6158
Ok(private_key)
6259
}
6360

64-
/// Decrypt PGP encrypted data using GPG directly
6561
pub fn decrypt_data(&self, encrypted_data: &str) -> Result<String> {
6662
let mut cmd = Command::new("gpg");
6763
cmd.args(["--decrypt", "--batch", "--quiet"]);
@@ -73,7 +69,6 @@ impl GpgManager {
7369
.spawn()
7470
.context("Failed to spawn gpg process for decryption")?;
7571

76-
// Write encrypted data to stdin
7772
if let Some(stdin) = child.stdin.take() {
7873
use std::io::Write;
7974
let mut stdin = stdin;

crates/secenv/src/main.rs

Lines changed: 116 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,24 @@ use {
1111
Result,
1212
},
1313
args::ManualFormat,
14+
manifest::{
15+
Content,
16+
ContentWrapper,
17+
EncodedValue,
18+
EncodedValueWrapper,
19+
Manifest,
20+
ManifestEnv,
21+
ManifestProfile,
22+
Secret,
23+
SecretAllocation,
24+
SecretAllocationWrapper,
25+
SecretWrapper,
26+
},
1427
regex::Regex,
1528
std::{
1629
collections::HashMap,
17-
process::Command,
1830
path::Path,
31+
process::Command,
1932
},
2033
};
2134

@@ -43,7 +56,12 @@ async fn main() -> Result<()> {
4356
crate::reference::build_shell_completion(&path, &shell)?;
4457
Ok(())
4558
},
46-
| crate::args::Command::Unlock { manifest, profile_name, command, force } => {
59+
| crate::args::Command::Unlock {
60+
manifest,
61+
profile_name,
62+
command,
63+
force,
64+
} => {
4765
let mut env_vars = HashMap::new();
4866
let mut pgp_manager = crate::pgp::PgpManager::new().context("Failed to initialize PGP manager")?;
4967

@@ -63,8 +81,6 @@ async fn main() -> Result<()> {
6381
},
6482
}
6583
}
66-
67-
// Prepare files from manifest before running command
6884
let mut created_files: Vec<String> = Vec::new();
6985
for (file_path, content) in profile.files.iter() {
7086
let absolute_path = Path::new(file_path);
@@ -74,7 +90,6 @@ async fn main() -> Result<()> {
7490
absolute_path.display()
7591
));
7692
}
77-
7893
let value = content.inner.get_value(&mut pgp_manager)?;
7994
if let Some(parent) = absolute_path.parent() {
8095
std::fs::create_dir_all(parent)
@@ -87,7 +102,7 @@ async fn main() -> Result<()> {
87102

88103
let exec_status: Result<Option<std::process::ExitStatus>> = match command {
89104
| Some(cmd_args) if !cmd_args.is_empty() => {
90-
let status = execute_command_with_env(&cmd_args, &env_vars, &profile.env.keep)?;
105+
let status = exec_command(&cmd_args, &env_vars, &profile.env.keep)?;
91106
Ok(Some(status))
92107
},
93108
| _ => {
@@ -98,15 +113,10 @@ async fn main() -> Result<()> {
98113
},
99114
};
100115

101-
// Cleanup files after command execution or printing envs
102116
let mut cleanup_error: Option<anyhow::Error> = None;
103117
for file in created_files.iter() {
104118
if let Err(e) = std::fs::remove_file(file) {
105-
cleanup_error = Some(anyhow::anyhow!(
106-
"Failed to remove file '{}': {}",
107-
file,
108-
e
109-
));
119+
cleanup_error = Some(anyhow::anyhow!("Failed to remove file '{}': {}", file, e));
110120
}
111121
}
112122

@@ -136,60 +146,110 @@ async fn main() -> Result<()> {
136146
));
137147
}
138148

139-
let example_config = r#"
140-
version = "0.1.0"
141-
142-
profiles = {
143-
default = {
144-
env = {
145-
# keep = ["^PATH$", "^LC_.*"] # Uncomment to only preserve matching host env vars
146-
vars = {
147-
# Example: plain literal value
148-
APP_NAME.plain.literal = "myapp"
149-
150-
# Example: plain base64-encoded value
151-
# DB_HOST.plain.base64 = "bG9jYWxob3N0" # "localhost" in base64
152-
153-
# Example: secure value using PGP secret from file
154-
# SECRET_TOKEN.secure {
155-
# secret.pgp.file = "/path/to/private.key"
156-
# value.literal = "-----BEGIN PGP MESSAGE-----..."
157-
# }
158-
159-
# Example: secure value using PGP secret from GCP (fully qualified resource)
160-
# API_KEY.secure {
161-
# secret.pgp.gcp.secret = "projects/myproject/secrets/my-pgp-key"
162-
# # secret.pgp.gcp.version = "latest" # optional
163-
# value.base64 = "<base64-encoded-ASCII-armored-message>"
164-
# }
165-
166-
# Example: secure value using inline private key
167-
# ENCRYPTED_TOKEN.secure {
168-
# secret.pgp.literal.literal = """
169-
# -----BEGIN PGP PRIVATE KEY BLOCK-----
170-
# ...
171-
# -----END PGP PRIVATE KEY BLOCK-----
172-
# """
173-
# value.literal = "-----BEGIN PGP MESSAGE-----..."
174-
# }
175-
}
176-
}
177-
}
178-
}
179-
"#;
149+
let mut profiles = HashMap::new();
150+
let mut vars = HashMap::new();
151+
152+
vars.insert("APP_NAME".to_string(), ContentWrapper {
153+
inner: Content::Plain(EncodedValue::Literal("myapp".to_string())),
154+
});
155+
156+
vars.insert("DB_HOST_EXAMPLE".to_string(), ContentWrapper {
157+
inner: Content::Plain(EncodedValue::Base64("bG9jYWxob3N0".to_string())),
158+
});
159+
160+
vars.insert("SECRET_TOKEN_EXAMPLE".to_string(), ContentWrapper {
161+
inner: Content::Secure {
162+
secret: SecretWrapper {
163+
inner: Secret::PGP(SecretAllocationWrapper {
164+
inner: SecretAllocation::File("/path/to/private.key".to_string()),
165+
}),
166+
},
167+
value: EncodedValueWrapper {
168+
inner: EncodedValue::Literal("-----BEGIN PGP MESSAGE-----...".to_string()),
169+
},
170+
},
171+
});
172+
173+
vars.insert("API_KEY_EXAMPLE".to_string(), ContentWrapper {
174+
inner: Content::Secure {
175+
secret: SecretWrapper {
176+
inner: Secret::PGP(SecretAllocationWrapper {
177+
inner: SecretAllocation::Gcp {
178+
secret: "projects/myproject/secrets/my-pgp-key".to_string(),
179+
version: Some("latest".to_string()),
180+
},
181+
}),
182+
},
183+
value: EncodedValueWrapper {
184+
inner: EncodedValue::Base64("<base64-encoded-ASCII-armored-message>".to_string()),
185+
},
186+
},
187+
});
188+
189+
vars.insert("GPG_ENCRYPTED_EXAMPLE".to_string(), ContentWrapper {
190+
inner: Content::Secure {
191+
secret: SecretWrapper {
192+
inner: Secret::PGP(SecretAllocationWrapper {
193+
inner: SecretAllocation::Gpg {
194+
fingerprint: "1E1BAC706C352094D490D5393F5167F1F3002043".to_string(),
195+
},
196+
}),
197+
},
198+
value: EncodedValueWrapper {
199+
inner: EncodedValue::Base64("<base64-encoded-ASCII-armored-message>".to_string()),
200+
},
201+
},
202+
});
203+
204+
let mut files = HashMap::new();
205+
206+
files.insert("./config.json".to_string(), ContentWrapper {
207+
inner: Content::Plain(EncodedValue::Literal("{\"key\": \"value\"}".to_string())),
208+
});
209+
210+
files.insert("./credentials.key".to_string(), ContentWrapper {
211+
inner: Content::Secure {
212+
secret: SecretWrapper {
213+
inner: Secret::PGP(SecretAllocationWrapper {
214+
inner: SecretAllocation::File("/path/to/private.key".to_string()),
215+
}),
216+
},
217+
value: EncodedValueWrapper {
218+
inner: EncodedValue::Literal("-----BEGIN PGP MESSAGE-----...".to_string()),
219+
},
220+
},
221+
});
222+
223+
let default_profile = ManifestProfile {
224+
files,
225+
env: ManifestEnv {
226+
keep: Some(vec!["^PATH$".to_string(), "^LC_.*".to_string()]),
227+
vars,
228+
},
229+
};
230+
231+
profiles.insert("default".to_string(), default_profile);
232+
233+
let manifest = Manifest {
234+
version: env!("CARGO_PKG_VERSION").to_string(),
235+
profiles,
236+
};
237+
238+
let json_config =
239+
serde_json::to_string_pretty(&manifest).context("Failed to serialize example config to JSON")?;
180240

181-
std::fs::write(&path, example_config)
241+
std::fs::write(&path, json_config)
182242
.with_context(|| format!("Failed to write config file: {}", path.display()))?;
183243

184244
println!("Created example configuration file: {}", path.display());
185245
println!("Edit the file to add your own variables and PGP keys.");
246+
println!("Note: Remove '_EXAMPLE' suffix from variable names before using them.");
186247
Ok(())
187248
},
188249
}
189250
}
190251

191-
/// Execute a command with the specified environment variables
192-
fn execute_command_with_env(
252+
fn exec_command(
193253
cmd_args: &[String],
194254
env_vars: &HashMap<String, String>,
195255
keep_env_vars: &Option<Vec<String>>,

crates/secenv/src/manifest.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ pub struct Manifest {
7979
pub struct ManifestProfile {
8080
#[serde(default)]
8181
pub files: HashMap<String, ContentWrapper>,
82+
8283
#[serde(default)]
8384
pub env: ManifestEnv,
8485
}
@@ -88,6 +89,7 @@ pub struct ManifestProfile {
8889
pub struct ManifestEnv {
8990
#[serde(default)]
9091
pub keep: Option<Vec<String>>,
92+
9193
#[serde(default)]
9294
pub vars: HashMap<String, ContentWrapper>,
9395
}
@@ -96,6 +98,7 @@ pub struct ManifestEnv {
9698
#[serde(rename_all = "snake_case")]
9799
pub enum Content {
98100
Plain(EncodedValue),
101+
99102
Secure {
100103
secret: SecretWrapper,
101104
value: EncodedValueWrapper,
@@ -162,13 +165,11 @@ impl Content {
162165
| Secret::PGP(allocation_wrapper) => {
163166
match &allocation_wrapper.inner {
164167
| SecretAllocation::Gpg { fingerprint: _ } => {
165-
// Use GPG directly for decryption when gpg variant is specified
166168
let gpg = GpgManager::new().context("Failed to initialize GPG manager")?;
167169
gpg.decrypt_data(&encrypted_data)
168170
.context("Failed to decrypt value with GPG")
169171
},
170172
| _ => {
171-
// For file-based or other PGP keys, use Sequoia-OpenPGP
172173
let pgp_key = allocation_wrapper.inner.get_value()?;
173174
pgp_manager
174175
.decrypt(&pgp_key, &encrypted_data)

0 commit comments

Comments
 (0)