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:
parent
98384791c3
commit
9b3ac63c3e
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -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
|
||||||
|
|
144
src/main.rs
144
src/main.rs
|
@ -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!");
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
Loading…
Reference in a new issue