Add interactive vault put - prompt for secrets instead of CLI args
All checks were successful
BotServer CI / build (push) Successful in 10m50s
All checks were successful
BotServer CI / build (push) Successful in 10m50s
This commit is contained in:
parent
c0b619b58f
commit
35b793d29c
4 changed files with 88 additions and 27 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -14,3 +14,4 @@ docs/book
|
||||||
*.rdb
|
*.rdb
|
||||||
botserver-installers
|
botserver-installers
|
||||||
.git-rewrite
|
.git-rewrite
|
||||||
|
vault-unseal-keys
|
||||||
|
|
@ -252,8 +252,9 @@ pub async fn run() -> Result<()> {
|
||||||
vault_migrate(env_file).await?;
|
vault_migrate(env_file).await?;
|
||||||
}
|
}
|
||||||
"put" => {
|
"put" => {
|
||||||
if args.len() < 5 {
|
if args.len() < 4 {
|
||||||
eprintln!("Usage: botserver vault put <path> <key=value> [key=value...]");
|
eprintln!("Usage: botserver vault put <path> [key=value] [key=value...]");
|
||||||
|
eprintln!(" botserver vault put <path> (interactive mode - prompts for keys)");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let path = &args[3];
|
let path = &args[3];
|
||||||
|
|
@ -610,20 +611,59 @@ async fn vault_put(path: &str, kvs: &[&str]) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut data: HashMap<String, String> = HashMap::new();
|
let mut data: HashMap<String, String> = HashMap::new();
|
||||||
for kv in kvs {
|
|
||||||
if let Some((k, v)) = kv.split_once('=') {
|
// If no key=value provided, enter interactive mode
|
||||||
data.insert(k.to_string(), v.to_string());
|
if kvs.is_empty() {
|
||||||
} else {
|
println!("\n=== Interactive Vault Store ===");
|
||||||
eprintln!("Invalid key=value pair: {}", kv);
|
println!("Path: {}", path);
|
||||||
|
println!("Enter values (press Enter with empty key to finish):\n");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
print!("Key: ");
|
||||||
|
std::io::Write::flush(&mut std::io::stdout())?;
|
||||||
|
let mut key = String::new();
|
||||||
|
std::io::stdin().read_line(&mut key)?;
|
||||||
|
let key = key.trim().to_string();
|
||||||
|
|
||||||
|
if key.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
print!("Value: ");
|
||||||
|
std::io::Write::flush(&mut std::io::stdout())?;
|
||||||
|
let mut value = String::new();
|
||||||
|
std::io::stdin().read_line(&mut value)?;
|
||||||
|
let value = value.trim().to_string();
|
||||||
|
|
||||||
|
if value.is_empty() {
|
||||||
|
eprintln!("Value cannot be empty, skipping '{}'", key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.insert(key.clone(), value);
|
||||||
|
println!("* Saved '{}'\n", key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.is_empty() {
|
||||||
|
return Err(anyhow::anyhow!("No values provided"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Command line mode
|
||||||
|
for kv in kvs {
|
||||||
|
if let Some((k, v)) = kv.split_once('=') {
|
||||||
|
data.insert(k.to_string(), v.to_string());
|
||||||
|
} else {
|
||||||
|
eprintln!("Invalid key=value pair: {}", kv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.is_empty() {
|
||||||
|
return Err(anyhow::anyhow!("No valid key=value pairs provided"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if data.is_empty() {
|
manager.put_secret(path, data.clone()).await?;
|
||||||
return Err(anyhow::anyhow!("No valid key=value pairs provided"));
|
println!("\n✓ Stored {} key(s) at {}", data.len(), path);
|
||||||
}
|
|
||||||
|
|
||||||
manager.put_secret(path, data).await?;
|
|
||||||
println!("* Stored {} key(s) at {}", kvs.len(), path);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -409,27 +409,52 @@ impl PackageManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assign_static_ip(container_name: &str) -> Result<String> {
|
fn assign_static_ip(container_name: &str) -> Result<String> {
|
||||||
// Pick a deterministic IP from the last 2 bytes of the MAC address to avoid collisions.
|
// Discover the host bridge gateway and subnet dynamically from the container's default route.
|
||||||
|
// The container already has IPv6 + a link on eth0, so we can read the bridge from the host.
|
||||||
|
let bridge_info = std::process::Command::new("ip")
|
||||||
|
.args(["-4", "addr", "show", "lxdbr0"])
|
||||||
|
.output()
|
||||||
|
.ok()
|
||||||
|
.and_then(|o| {
|
||||||
|
let out = String::from_utf8_lossy(&o.stdout).to_string();
|
||||||
|
// Extract "10.x.x.x/prefix" from "inet 10.x.x.x/24 ..."
|
||||||
|
out.lines()
|
||||||
|
.find(|l| l.contains("inet "))
|
||||||
|
.and_then(|l| l.split_whitespace().nth(1))
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
});
|
||||||
|
|
||||||
|
let (gateway, prefix) = match bridge_info.as_deref().and_then(|cidr| {
|
||||||
|
let (ip, pfx_str) = cidr.split_once('/')?;
|
||||||
|
let parts: Vec<&str> = ip.split('.').collect();
|
||||||
|
let base = format!("{}.{}.{}.", parts[0], parts[1], parts[2]);
|
||||||
|
let pfx = pfx_str.parse::<u8>().ok()?;
|
||||||
|
Some((ip.to_string(), format!("{base}{{octet}}/{pfx}")))
|
||||||
|
}) {
|
||||||
|
Some(pair) => pair,
|
||||||
|
None => return Err(anyhow::anyhow!("Cannot determine lxdbr0 subnet")),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Derive last octet from MAC to avoid collisions (range 100-249).
|
||||||
let mac_out = safe_lxc(&["exec", container_name, "--", "cat", "/sys/class/net/eth0/address"]);
|
let mac_out = safe_lxc(&["exec", container_name, "--", "cat", "/sys/class/net/eth0/address"]);
|
||||||
let last_octet = mac_out
|
let octet = mac_out
|
||||||
.and_then(|o| o.status.success().then(|| String::from_utf8_lossy(&o.stdout).trim().to_string()))
|
.and_then(|o| o.status.success().then(|| String::from_utf8_lossy(&o.stdout).trim().to_string()))
|
||||||
.and_then(|mac| {
|
.and_then(|mac| {
|
||||||
let parts: Vec<&str> = mac.split(':').collect();
|
let parts: Vec<&str> = mac.split(':').collect();
|
||||||
let a = u8::from_str_radix(parts.get(4)?, 16).ok()?;
|
let a = u8::from_str_radix(parts.get(4)?, 16).ok()?;
|
||||||
let b = u8::from_str_radix(parts.get(5)?, 16).ok()?;
|
let b = u8::from_str_radix(parts.get(5)?, 16).ok()?;
|
||||||
// Map into 100-250 range to avoid gateway (.1) and broadcast (.255)
|
Some(100u16 + (u16::from(a) * 256 + u16::from(b)) % 150)
|
||||||
Some(100u16 + ((u16::from(a) * 256 + u16::from(b)) % 150) as u16)
|
|
||||||
})
|
})
|
||||||
.unwrap_or(100);
|
.unwrap_or(100);
|
||||||
|
|
||||||
let ip = format!("10.43.228.{last_octet}");
|
let cidr = prefix.replace("{octet}", &octet.to_string());
|
||||||
let cidr = format!("{ip}/24");
|
let ip = cidr.split('/').next().unwrap_or("").to_string();
|
||||||
|
|
||||||
safe_lxc(&["exec", container_name, "--", "ip", "addr", "add", &cidr, "dev", "eth0"]);
|
safe_lxc(&["exec", container_name, "--", "ip", "addr", "add", &cidr, "dev", "eth0"]);
|
||||||
safe_lxc(&["exec", container_name, "--", "ip", "route", "add", "default", "via", "10.43.228.1"]);
|
safe_lxc(&["exec", container_name, "--", "ip", "route", "add", "default", "via", &gateway]);
|
||||||
safe_lxc(&["exec", container_name, "--", "bash", "-c",
|
safe_lxc(&["exec", container_name, "--", "bash", "-c",
|
||||||
&format!("printf 'nameserver 8.8.8.8\\nnameserver 8.8.4.4\\n' > /etc/resolv.conf && \
|
&format!("printf 'nameserver 8.8.8.8\\nnameserver 8.8.4.4\\n' > /etc/resolv.conf && \
|
||||||
printf '#!/bin/sh\\nip addr add {cidr} dev eth0 2>/dev/null||true\\nip route add default via 10.43.228.1 2>/dev/null||true\\nexit 0\\n' > /etc/rc.local && \
|
printf '#!/bin/sh\\nip addr add {cidr} dev eth0 2>/dev/null||true\\nip route add default via {gateway} 2>/dev/null||true\\nexit 0\\n' > /etc/rc.local && \
|
||||||
chmod +x /etc/rc.local && systemctl enable rc-local 2>/dev/null||true")]);
|
chmod +x /etc/rc.local && systemctl enable rc-local 2>/dev/null||true")]);
|
||||||
|
|
||||||
info!("Assigned static IP {} to container '{}'", ip, container_name);
|
info!("Assigned static IP {} to container '{}'", ip, container_name);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
Unseal Key 1: KNMpbHTx0zYKG+P8oKoFNe7KKK7gEFCJ/IeoyXmHJpcb
|
|
||||||
Unseal Key 2: XoTPcUpbE4j5u6AeIjVhzWd1ku4U5NCANlPLf8pJMA3y
|
|
||||||
Unseal Key 3: sGEbMnQxDmS5itqsdNacia+glPKBIzLk2/9jntEkAIwo
|
|
||||||
Unseal Key 4: 9jPawRkzt0nmuR4V+KeecMns2in3pj+fQFqLfsfyimN1
|
|
||||||
Unseal Key 5: JO0Bi3UXibXdBMTcCPNmmghXhLNcV14035KkZhc3kU1j
|
|
||||||
Loading…
Add table
Reference in a new issue