Implement JSON credential storage

- Add JSON format for storing Vault credentials
- Update save_credentials function to support both formats
- Save both .json and .txt files for compatibility
- Update test_docker.sh to use jq for reliable JSON parsing
- Improve key extraction for unseal operations
- Update .gitignore to exclude JSON credentials

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Harald Hoyer 2025-03-20 13:16:39 +01:00
parent 98384791c3
commit 9b3ac63c3e
3 changed files with 150 additions and 159 deletions

1
.gitignore vendored
View file

@ -13,6 +13,7 @@ Cargo.lock
# Vault related files # Vault related files
vault-credentials.txt vault-credentials.txt
vault-credentials.json
vault-config/ vault-config/
# Temporary test files # Temporary test files

View file

@ -6,7 +6,6 @@ use std::{
fs::File, fs::File,
io::Write, io::Write,
path::Path, path::Path,
process::Command,
time::Duration, time::Duration,
}; };
use tokio::time::sleep; use tokio::time::sleep;
@ -40,6 +39,21 @@ struct UnsealRequest {
// Function to save Vault credentials to a file // Function to save Vault credentials to a file
fn save_credentials(response: &InitResponse, file_path: &str) -> Result<()> { fn save_credentials(response: &InitResponse, file_path: &str) -> Result<()> {
// For JSON output
if file_path.ends_with(".json") {
let json = serde_json::json!({
"keys": response.keys,
"keys_base64": response.keys_base64,
"root_token": response.root_token
});
let mut file = File::create(Path::new(file_path))?;
file.write_all(serde_json::to_string_pretty(&json)?.as_bytes())?;
println!("Credentials saved to JSON file: {}", file_path);
return Ok(());
}
// For plaintext output (legacy format)
let mut file = File::create(Path::new(file_path))?; let mut file = File::create(Path::new(file_path))?;
writeln!(file, "Unseal Keys:")?; writeln!(file, "Unseal Keys:")?;
for (i, key) in response.keys.iter().enumerate() { for (i, key) in response.keys.iter().enumerate() {
@ -92,72 +106,6 @@ async fn wait_for_vault(addr: &str) -> Result<()> {
Ok(()) Ok(())
} }
// Function to copy credentials to a mounted volume if available
fn copy_credentials_to_volume(src_path: &str) -> Result<()> {
println!("Searching for credentials file...");
if let Ok(metadata) = std::fs::metadata(src_path) {
if metadata.is_file() {
println!("Found credentials at {}, copying...", src_path);
// Create the data directory if it doesn't exist
if let Err(e) = std::fs::create_dir_all("/app/data") {
println!("Warning: Couldn't create /app/data directory: {}", e);
} else {
let dest_path = "/app/data/vault-credentials.txt";
// Check if source and destination are the same
if src_path == dest_path {
println!("Source and destination are the same file, skipping copy");
} else {
match std::fs::copy(src_path, dest_path) {
Ok(_) => println!("Credentials saved to {}", dest_path),
Err(e) => println!("Failed to copy credentials: {}", e),
}
}
}
}
} else {
// If the file doesn't exist in the current directory, search for it
let output = Command::new("find")
.args(["/", "-name", "vault-credentials.txt", "-type", "f"])
.output();
match output {
Ok(output) => {
let files = String::from_utf8_lossy(&output.stdout);
let files: Vec<&str> = files.split('\n').filter(|s| !s.is_empty()).collect();
if !files.is_empty() {
println!("Found credentials at {}, copying...", files[0]);
// Create the data directory if it doesn't exist
if let Err(e) = std::fs::create_dir_all("/app/data") {
println!("Warning: Couldn't create /app/data directory: {}", e);
} else {
let dest_path = "/app/data/vault-credentials.txt";
// Check if source and destination are the same
if files[0] == dest_path {
println!("Source and destination are the same file, skipping copy");
} else {
match std::fs::copy(files[0], dest_path) {
Ok(_) => println!("Credentials saved to {}", dest_path),
Err(e) => println!("Failed to copy credentials: {}", e),
}
}
}
} else {
println!("Could not find credentials file");
}
},
Err(e) => println!("Failed to search for credentials: {}", e),
}
}
Ok(())
}
async fn check_init_status(client: &Client, addr: &str) -> Result<bool> { async fn check_init_status(client: &Client, addr: &str) -> Result<bool> {
println!("Checking if Vault is already initialized..."); println!("Checking if Vault is already initialized...");
@ -358,17 +306,28 @@ async fn main() -> Result<()> {
let init_response = init_vault(&client, &vault_addr).await?; let init_response = init_vault(&client, &vault_addr).await?;
// Save credentials to files // Save credentials to files
println!("Saving credentials to file..."); println!("Saving credentials to files...");
let current_dir = std::env::current_dir().context("Failed to get current directory")?; let current_dir = std::env::current_dir().context("Failed to get current directory")?;
let cred_path = current_dir.join("vault-credentials.txt");
save_credentials(&init_response, cred_path.to_str().unwrap())?; // Save as JSON (new format)
println!("Credentials saved to: {}", cred_path.display()); let json_path = current_dir.join("vault-credentials.json");
save_credentials(&init_response, json_path.to_str().unwrap())?;
println!("JSON credentials saved to: {}", json_path.display());
// Save as text (for backward compatibility)
let text_path = current_dir.join("vault-credentials.txt");
save_credentials(&init_response, text_path.to_str().unwrap())?;
println!("Text credentials saved to: {}", text_path.display());
// Also save to /app/data as a backup for Docker volume mounting // Also save to /app/data as a backup for Docker volume mounting
if let Ok(()) = std::fs::create_dir_all("/app/data") { if let Ok(()) = std::fs::create_dir_all("/app/data") {
let docker_path = "/app/data/vault-credentials.txt"; let docker_json_path = "/app/data/vault-credentials.json";
save_credentials(&init_response, docker_path)?; save_credentials(&init_response, docker_json_path)?;
println!("Backup credentials saved to Docker volume at: {}", docker_path); println!("Backup JSON credentials saved to Docker volume at: {}", docker_json_path);
let docker_text_path = "/app/data/vault-credentials.txt";
save_credentials(&init_response, docker_text_path)?;
println!("Backup text credentials saved to Docker volume at: {}", docker_text_path);
} }
println!("========================================="); println!("=========================================");
@ -389,22 +348,33 @@ async fn main() -> Result<()> {
unseal_vault(&client, &vault_addr, &unseal_keys).await?; unseal_vault(&client, &vault_addr, &unseal_keys).await?;
println!("Vault is now initialized and unsealed");
// Store the root token and unseal keys in environment variables
// Using unsafe block as set_var is now considered unsafe in recent Rust
unsafe {
env::set_var("VAULT_TOKEN", &init_response.root_token);
for (i, key) in init_response.keys_base64.iter().enumerate() {
env::set_var(format!("VAULT_UNSEAL_KEY_{}", i + 1), key);
}
}
println!("Vault initialization and unseal complete!"); println!("Vault initialization and unseal complete!");
} }
// Copy credentials to the mounted volume (former docker-entrypoint.sh functionality) // Look for any existing credentials and copy them to the mounted volume
copy_credentials_to_volume("vault-credentials.txt")?; if let Ok(metadata) = std::fs::metadata("vault-credentials.json") {
if metadata.is_file() {
println!("Found JSON credentials file, ensuring it's saved to Docker volume...");
if let Ok(()) = std::fs::create_dir_all("/app/data") {
match std::fs::copy("vault-credentials.json", "/app/data/vault-credentials.json") {
Ok(_) => println!("JSON credentials saved to Docker volume"),
Err(e) => println!("Failed to copy JSON credentials: {}", e),
}
}
}
}
if let Ok(metadata) = std::fs::metadata("vault-credentials.txt") {
if metadata.is_file() {
println!("Found text credentials file, ensuring it's saved to Docker volume...");
if let Ok(()) = std::fs::create_dir_all("/app/data") {
match std::fs::copy("vault-credentials.txt", "/app/data/vault-credentials.txt") {
Ok(_) => println!("Text credentials saved to Docker volume"),
Err(e) => println!("Failed to copy text credentials: {}", e),
}
}
}
}
println!("Operation complete!"); println!("Operation complete!");

View file

@ -99,22 +99,28 @@ wait_for_vault_init() {
# Wait for vault-init to complete # Wait for vault-init to complete
wait_for_vault_init wait_for_vault_init
# Check if vault-credentials.txt was created # Check if vault-credentials.json was created
if [ -f "vault-credentials.txt" ]; then if [ -f "vault-credentials.json" ]; then
log "INFO" "Credentials file was created successfully" log "INFO" "JSON credentials file was created successfully"
else else
log "ERROR" "Credentials file was not created" log "ERROR" "JSON credentials file was not created"
exit 1 exit 1
fi fi
# Verify the content of vault-credentials.txt # Verify the content of vault-credentials.json
if grep -q "Unseal Keys:" vault-credentials.txt && grep -q "Root Token:" vault-credentials.txt; then if jq -e '.keys_base64 | length' vault-credentials.json >/dev/null && \
log "INFO" "Credentials file contains expected content" jq -e '.root_token' vault-credentials.json >/dev/null; then
log "INFO" "JSON credentials file contains expected content"
else else
log "ERROR" "Credentials file doesn't contain expected content" log "ERROR" "JSON credentials file doesn't contain expected content"
exit 1 exit 1
fi fi
# Also check for backward compatibility
if [ -f "vault-credentials.txt" ]; then
log "INFO" "Text credentials file was also created (for backward compatibility)"
fi
# Verify Vault is unsealed after initial setup # Verify Vault is unsealed after initial setup
vault_status=$(docker-compose exec -T vault env VAULT_ADDR=http://127.0.0.1:8200 vault status -format=json 2>/dev/null || echo '{"sealed": true}') vault_status=$(docker-compose exec -T vault env VAULT_ADDR=http://127.0.0.1:8200 vault status -format=json 2>/dev/null || echo '{"sealed": true}')
@ -157,28 +163,35 @@ else
echo $vault_status echo $vault_status
fi fi
# Extract keys from credentials file and root token # Extract keys from JSON credentials file
log "INFO" "Extracting unseal keys and root token from credentials file..." log "INFO" "Extracting unseal keys and root token from JSON credentials file..."
unseal_keys=$(grep "Base64 Unseal Keys:" -A 3 vault-credentials.txt | grep "Key" | awk '{print $3}') # Using jq to extract the first 3 unseal keys (as that's the threshold)
root_token=$(grep "Root Token:" vault-credentials.txt | awk '{print $3}') unseal_keys=$(jq -r '.keys_base64[0:3][]' vault-credentials.json)
root_token=$(jq -r '.root_token' vault-credentials.json)
# First, try running 'vault operator unseal' directly for a more robust test # First, try running 'vault operator unseal' directly for a more robust test
log "INFO" "Attempting to unseal Vault directly with unseal keys..." log "INFO" "Attempting to unseal Vault directly with unseal keys..."
key1=$(echo "$unseal_keys" | head -n 1) # Using an array to capture the keys
key2=$(echo "$unseal_keys" | head -n 2 | tail -n 1) readarray -t key_array <<< "$unseal_keys"
key3=$(echo "$unseal_keys" | head -n 3 | tail -n 1)
docker-compose exec -T vault env VAULT_ADDR=http://127.0.0.1:8200 vault operator unseal "$key1" for key in "${key_array[@]}"; do
docker-compose exec -T vault env VAULT_ADDR=http://127.0.0.1:8200 vault operator unseal "$key2" log "INFO" "Applying unseal key: ${key:0:8}..." # Show only first 8 chars for security
docker-compose exec -T vault env VAULT_ADDR=http://127.0.0.1:8200 vault operator unseal "$key3" docker-compose exec -T vault env VAULT_ADDR=http://127.0.0.1:8200 vault operator unseal "$key"
done
# As a fallback, also try running vault-init with environment variables # As a fallback, also try running vault-init with environment variables
log "INFO" "Starting vault-init with environment variables..." log "INFO" "Starting vault-init with environment variables..."
docker-compose run -e VAULT_ADDR=http://vault:8200 \ # Check how many keys we have
-e VAULT_UNSEAL_KEY_1=$(echo "$unseal_keys" | head -n 1) \ key_count=${#key_array[@]}
-e VAULT_UNSEAL_KEY_2=$(echo "$unseal_keys" | head -n 2 | tail -n 1) \ env_vars="-e VAULT_ADDR=http://vault:8200"
-e VAULT_UNSEAL_KEY_3=$(echo "$unseal_keys" | head -n 3 | tail -n 1) \
--rm vault-init # Add each key to environment variables
for i in $(seq 0 $((key_count-1))); do
env_vars="$env_vars -e VAULT_UNSEAL_KEY_$((i+1))=${key_array[$i]}"
done
# Run the command with all environment variables
docker-compose run $env_vars --rm vault-init
# Verify Vault is unsealed now # Verify Vault is unsealed now
vault_status=$(docker-compose exec -T vault env VAULT_ADDR=http://127.0.0.1:8200 vault status -format=json 2>/dev/null || echo '{"sealed": true}') vault_status=$(docker-compose exec -T vault env VAULT_ADDR=http://127.0.0.1:8200 vault status -format=json 2>/dev/null || echo '{"sealed": true}')
@ -197,9 +210,16 @@ fi
# Test some basic Vault operations # Test some basic Vault operations
log "INFO" "Testing basic Vault operations..." log "INFO" "Testing basic Vault operations..."
# Write a secret # Write a secret using the root token from JSON credentials
token_result=$(docker-compose exec -T vault env VAULT_ADDR=http://127.0.0.1:8200 vault login "$root_token" 2>&1) token_result=$(docker-compose exec -T vault env VAULT_ADDR=http://127.0.0.1:8200 vault login "$root_token" 2>&1)
log "INFO" "Login result: $(echo "$token_result" | grep "Success")" login_success=$(echo "$token_result" | grep -c "Success" || echo "0")
if [ "$login_success" -gt 0 ]; then
log "INFO" "Successfully logged in with root token"
else
log "ERROR" "Failed to log in with root token"
echo "$token_result"
exit 1
fi
# Enable KV secrets engine # Enable KV secrets engine
enable_result=$(docker-compose exec -T vault env VAULT_ADDR=http://127.0.0.1:8200 vault secrets enable -path=kv kv 2>&1 || echo "KV already enabled") enable_result=$(docker-compose exec -T vault env VAULT_ADDR=http://127.0.0.1:8200 vault secrets enable -path=kv kv 2>&1 || echo "KV already enabled")