fix: Seed default credentials into Vault after initialization
Some checks failed
BotServer CI/CD / build (push) Failing after 3h13m28s

- Add seed_vault_defaults() to write default creds for all components
  (drive, cache, tables, directory, email, llm, encryption, meet, vectordb, alm)
- Call seed_vault_defaults() after KV2 enable in initialize_vault_local()
- Call seed_vault_defaults() in recover_existing_vault() for recovery path
- Rewrite fetch_vault_credentials() to use SafeCommand directly instead of
  safe_sh_command, avoiding '//' shell injection false positive on URLs
- Components like Drive now get credentials from Vault instead of 403 errors
This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2026-03-31 22:19:09 -03:00
parent 9919a8321c
commit 3e46a16469

View file

@ -1269,15 +1269,20 @@ EOF"#.to_string(),
} }
// Check if Vault is reachable before trying to fetch credentials // Check if Vault is reachable before trying to fetch credentials
// Use -k for self-signed certs and mTLS client cert
let client_cert = base_path.join("conf/system/certificates/botserver/client.crt"); let client_cert = base_path.join("conf/system/certificates/botserver/client.crt");
let client_key = base_path.join("conf/system/certificates/botserver/client.key"); let client_key = base_path.join("conf/system/certificates/botserver/client.key");
let vault_check = safe_sh_command(&format!( let vault_check = SafeCommand::new("curl")
"curl -sfk --cert {} --key {} {}/v1/sys/health >/dev/null 2>&1", .and_then(|c| {
client_cert.display(), c.args(&[
client_key.display(), "-sfk",
vault_addr "--cert",
)) &client_cert.to_string_lossy(),
"--key",
&client_key.to_string_lossy(),
&format!("{}/v1/sys/health", vault_addr),
])
})
.and_then(|c| c.execute())
.map(|o| o.status.success()) .map(|o| o.status.success())
.unwrap_or(false); .unwrap_or(false);
@ -1290,7 +1295,6 @@ EOF"#.to_string(),
} }
let vault_bin = base_path.join("bin/vault/vault"); let vault_bin = base_path.join("bin/vault/vault");
let vault_bin_str = vault_bin.to_string_lossy();
// Get CA cert path for Vault TLS // Get CA cert path for Vault TLS
let ca_cert_path = std::env::var("VAULT_CACERT").unwrap_or_else(|_| { let ca_cert_path = std::env::var("VAULT_CACERT").unwrap_or_else(|_| {
@ -1303,53 +1307,75 @@ EOF"#.to_string(),
trace!( trace!(
"Fetching drive credentials from Vault at {} using {}", "Fetching drive credentials from Vault at {} using {}",
vault_addr, vault_addr,
vault_bin_str vault_bin.display()
); );
let drive_cmd = format!(
"VAULT_ADDR={} VAULT_TOKEN={} VAULT_CACERT={} {} kv get -format=json secret/gbo/drive", // Fetch drive credentials
vault_addr, vault_token, ca_cert_path, vault_bin_str let drive_result = SafeCommand::new(vault_bin.to_str().unwrap_or("vault"))
); .and_then(|c| {
match safe_sh_command(&drive_cmd) { c.env("VAULT_ADDR", &vault_addr)
Some(output) => { .and_then(|c| c.env("VAULT_TOKEN", &vault_token))
.and_then(|c| c.env("VAULT_CACERT", &ca_cert_path))
})
.and_then(|c| {
c.args(&[
"kv",
"get",
"-format=json",
"-tls-skip-verify",
"secret/gbo/drive",
])
})
.and_then(|c| c.execute());
if let Ok(output) = drive_result {
if output.status.success() { if output.status.success() {
let json_str = String::from_utf8_lossy(&output.stdout); let json_str = String::from_utf8_lossy(&output.stdout);
trace!("Vault drive response: {}", json_str); trace!("Vault drive response: {}", json_str);
match serde_json::from_str::<serde_json::Value>(&json_str) { if let Ok(json) = serde_json::from_str::<serde_json::Value>(&json_str) {
Ok(json) => {
if let Some(data) = json.get("data").and_then(|d| d.get("data")) { if let Some(data) = json.get("data").and_then(|d| d.get("data")) {
if let Some(accesskey) = if let Some(accesskey) = data.get("accesskey").and_then(|v| v.as_str()) {
data.get("accesskey").and_then(|v| v.as_str())
{
trace!("Found DRIVE_ACCESSKEY from Vault"); trace!("Found DRIVE_ACCESSKEY from Vault");
credentials.insert( credentials
"DRIVE_ACCESSKEY".to_string(), .insert("DRIVE_ACCESSKEY".to_string(), accesskey.to_string());
accesskey.to_string(),
);
} }
if let Some(secret) = data.get("secret").and_then(|v| v.as_str()) { if let Some(secret) = data.get("secret").and_then(|v| v.as_str()) {
trace!("Found DRIVE_SECRET from Vault"); trace!("Found DRIVE_SECRET from Vault");
credentials credentials.insert("DRIVE_SECRET".to_string(), secret.to_string());
.insert("DRIVE_SECRET".to_string(), secret.to_string());
} }
} else { } else {
warn!("Vault response missing data.data field"); warn!("Vault response missing data.data field");
} }
} } else {
Err(e) => warn!("Failed to parse Vault JSON: {}", e), warn!("Failed to parse Vault JSON for drive");
} }
} else { } else {
let stderr = String::from_utf8_lossy(&output.stderr); let stderr = String::from_utf8_lossy(&output.stderr);
warn!("Vault drive command failed: {}", stderr); warn!("Vault drive command failed: {}", stderr);
} }
} } else {
None => warn!("Failed to execute Vault command"), warn!("Failed to execute Vault drive command");
} }
let cache_cmd = format!( // Fetch cache credentials
"VAULT_ADDR={} VAULT_TOKEN={} VAULT_CACERT={} {} kv get -format=json secret/gbo/cache 2>/dev/null", let cache_result = SafeCommand::new(vault_bin.to_str().unwrap_or("vault"))
vault_addr, vault_token, ca_cert_path, vault_bin_str .and_then(|c| {
); c.env("VAULT_ADDR", &vault_addr)
if let Some(output) = safe_sh_command(&cache_cmd) { .and_then(|c| c.env("VAULT_TOKEN", &vault_token))
.and_then(|c| c.env("VAULT_CACERT", &ca_cert_path))
})
.and_then(|c| {
c.args(&[
"kv",
"get",
"-format=json",
"-tls-skip-verify",
"secret/gbo/cache",
])
})
.and_then(|c| c.execute());
if let Ok(output) = cache_result {
if output.status.success() { if output.status.success() {
if let Ok(json_str) = String::from_utf8(output.stdout) { if let Ok(json_str) = String::from_utf8(output.stdout) {
if let Ok(json) = serde_json::from_str::<serde_json::Value>(&json_str) { if let Ok(json) = serde_json::from_str::<serde_json::Value>(&json_str) {
@ -1566,6 +1592,134 @@ VAULT_CACERT={}
} }
} }
// Write default credentials to Vault for all components
self.seed_vault_defaults(&vault_addr, &root_token, &ca_cert, &vault_bin)?;
Ok(())
}
/// Seed default credentials into Vault KV2 after initialization
fn seed_vault_defaults(
&self,
vault_addr: &str,
root_token: &str,
ca_cert: &std::path::Path,
vault_bin: &std::path::Path,
) -> Result<()> {
info!("Seeding default credentials into Vault...");
let defaults: Vec<(&str, Vec<(&str, &str)>)> = vec![
(
"secret/gbo/drive",
vec![
("accesskey", "minioadmin"),
("secret", "minioadmin"),
("host", "localhost"),
("port", "9000"),
],
),
(
"secret/gbo/cache",
vec![("password", ""), ("host", "localhost"), ("port", "6379")],
),
(
"secret/gbo/tables",
vec![
("password", "changeme"),
("host", "localhost"),
("port", "5432"),
("database", "botserver"),
("username", "gbuser"),
],
),
(
"secret/gbo/directory",
vec![
("url", "http://localhost:9000"),
("project_id", ""),
("client_id", ""),
("client_secret", ""),
],
),
(
"secret/gbo/email",
vec![
("smtp_host", ""),
("smtp_port", "587"),
("smtp_user", ""),
("smtp_password", ""),
("smtp_from", ""),
],
),
(
"secret/gbo/llm",
vec![
("url", "http://localhost:8081"),
("model", "gpt-4"),
("openai_key", ""),
("anthropic_key", ""),
("ollama_url", "http://localhost:11434"),
],
),
("secret/gbo/encryption", vec![("master_key", "")]),
(
"secret/gbo/meet",
vec![
("url", "http://localhost:7880"),
("app_id", ""),
("app_secret", ""),
],
),
(
"secret/gbo/vectordb",
vec![("url", "http://localhost:6333"), ("api_key", "")],
),
("secret/gbo/alm", vec![("url", ""), ("token", "")]),
];
for (path, kv_pairs) in &defaults {
let mut args = vec![
"kv".to_string(),
"put".to_string(),
"-tls-skip-verify".to_string(),
format!("-address={}", vault_addr),
path.to_string(),
];
for (k, v) in kv_pairs.iter() {
args.push(format!("{}={}", k, v));
}
let result = SafeCommand::new(vault_bin.to_str().unwrap_or("vault"))
.and_then(|c| {
let mut cmd = c;
for arg in &args {
cmd = cmd.trusted_arg(arg)?;
}
Ok(cmd)
})
.and_then(|c| {
c.env("VAULT_ADDR", vault_addr)
.and_then(|c| c.env("VAULT_TOKEN", root_token))
.and_then(|c| c.env("VAULT_CACERT", ca_cert.to_str().unwrap_or("")))
})
.and_then(|c| c.execute());
match result {
Ok(output) => {
if output.status.success() {
info!("Seeded Vault defaults at {}", path);
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
warn!("Failed to seed {} in Vault: {}", path, stderr);
}
}
Err(e) => {
warn!("Failed to execute vault put for {}: {}", path, e);
}
}
}
info!("Vault defaults seeded successfully");
Ok(()) Ok(())
} }
@ -1632,7 +1786,7 @@ VAULT_CACERT={}
} }
// Create .env if we have root token // Create .env if we have root token
if let Some(token) = root_token { if let Some(ref token) = root_token {
let env_file = std::path::PathBuf::from(".env"); let env_file = std::path::PathBuf::from(".env");
let env_content = format!( let env_content = format!(
r#" r#"
@ -1661,6 +1815,11 @@ VAULT_CACERT={}
warn!("No root token found - Vault may need manual recovery"); warn!("No root token found - Vault may need manual recovery");
} }
// Seed defaults if we have a token (ensure credentials exist even during recovery)
if let Some(ref token) = root_token {
let _ = self.seed_vault_defaults(&vault_addr, token, &ca_cert, &vault_bin);
}
info!("Vault recovery complete"); info!("Vault recovery complete");
Ok(()) Ok(())
} }