diff --git a/Cargo.toml b/Cargo.toml index b241aae..3c7dfe2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,5 @@ axum = { version = "0.6.18", features = ["multipart"] } uuid = { version = "1.3.0", features = ["v4", "serde"] } sha2 = "0.10.6" base64 = "0.21.0" -tower = "0.4.13" -tower-http = { version = "0.4.0", features = ["cors"] } -futures = "0.3.28" +tracing = "0.1.37" +tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } diff --git a/src/api.rs b/src/api.rs index 0c35b22..7e6538b 100644 --- a/src/api.rs +++ b/src/api.rs @@ -10,6 +10,7 @@ use axum::{ use serde::{Deserialize, Serialize}; use std::sync::Arc; use tokio::net::TcpListener; +use tracing::{info, error, debug, instrument}; use crate::document_service::{Document, DocumentService, SignatureVerification}; use crate::vault_setup::VaultClient; @@ -48,6 +49,7 @@ pub struct SignDocumentRequest { // API response implementations impl IntoResponse for ApiError { fn into_response(self) -> Response { + error!("API error: {}", self.0); ( StatusCode::INTERNAL_SERVER_ERROR, format!("Error: {}", self.0), @@ -66,12 +68,13 @@ where } // Start the API server +#[instrument(skip(vault_addr, root_token))] pub async fn start_api( vault_addr: &str, root_token: &str, api_port: u16, ) -> Result<()> { - println!("Starting API server on port {}...", api_port); + info!("Starting API server on port {}...", api_port); // Initialize Vault client let vault_client = VaultClient::new(vault_addr, root_token); @@ -101,14 +104,17 @@ pub async fn start_api( .route("/api/documents/:id/verify", get(verify_document)) .with_state(state); + info!("API routes configured"); + // Start server let listener = TcpListener::bind(format!("0.0.0.0:{}", api_port)).await?; - println!("API server started on port {}", api_port); + info!("API server started on port {}", api_port); // Get the socket address let addr = listener.local_addr()?; // Bind and serve + info!("Serving API at {}", addr); Server::bind(&addr) .serve(app.into_make_service()) .await?; @@ -117,27 +123,36 @@ pub async fn start_api( } // Health check endpoint +#[instrument] async fn health_check() -> &'static str { + debug!("Health check endpoint called"); "OK" } // Login endpoint +#[instrument(skip(state, request), fields(username = %request.username))] async fn login( State(state): State, Json(request): Json, ) -> Result, ApiError> { + info!("Login attempt for user: {}", request.username); + let token = state.vault_client .login_user(&request.username, &request.password) .await?; + info!("User {} successfully authenticated", request.username); Ok(Json(LoginResponse { token })) } // Upload document endpoint +#[instrument(skip(state, multipart))] async fn upload_document( State(state): State, mut multipart: Multipart, ) -> Result, ApiError> { + info!("Document upload request received"); + let mut document_name = String::new(); let mut document_content = Vec::new(); @@ -147,12 +162,15 @@ async fn upload_document( if name == "name" { document_name = field.text().await?; + debug!("Received document name: {}", document_name); } else if name == "file" { document_content = field.bytes().await?.to_vec(); + debug!("Received document content: {} bytes", document_content.len()); } } if document_name.is_empty() || document_content.is_empty() { + error!("Missing document name or content"); return Err(anyhow::anyhow!("Missing document name or content").into()); } @@ -166,27 +184,37 @@ async fn upload_document( .get_document(&document_id) .await?; + info!("Document uploaded successfully with ID: {}", document_id); Ok(Json(document)) } // Get document endpoint +#[instrument(skip(state))] async fn get_document( State(state): State, Path(document_id): Path, ) -> Result, ApiError> { + info!("Fetching document: {}", document_id); + let document = state.document_service .get_document(&document_id) .await?; + debug!("Retrieved document {} with {} signatures", + document.id, document.signatures.len()); + Ok(Json(document)) } // Sign document endpoint +#[instrument(skip(state, request), fields(document_id = %document_id, username = %request.username))] async fn sign_document( State(state): State, Path(document_id): Path, Json(request): Json, ) -> Result, ApiError> { + info!("Signing request for document {} by user {}", document_id, request.username); + state.document_service .sign_document(&document_id, &request.username, &request.token) .await?; @@ -195,17 +223,24 @@ async fn sign_document( .get_document(&document_id) .await?; + info!("Document {} successfully signed by {}", document_id, request.username); Ok(Json(document)) } // Verify document endpoint +#[instrument(skip(state))] async fn verify_document( State(state): State, Path(document_id): Path, ) -> Result, ApiError> { + info!("Verifying document signatures: {}", document_id); + let verification = state.document_service .verify_document_signatures(&document_id) .await?; + info!("Document {} verification result: {}", + document_id, if verification.is_verified { "VERIFIED" } else { "PENDING" }); + Ok(Json(verification)) } diff --git a/src/document_service.rs b/src/document_service.rs index 2a3c360..41f357f 100644 --- a/src/document_service.rs +++ b/src/document_service.rs @@ -6,6 +6,7 @@ use sha2::{Sha256, Digest}; use std::collections::HashMap; use uuid::Uuid; use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64}; +use tracing::{info, error, debug, instrument}; use crate::vault_setup::{Department, User, VaultClient}; @@ -52,14 +53,19 @@ impl DocumentService { } // Upload a new document and store its metadata + #[instrument(skip(self, content), fields(document_name = %name))] pub async fn upload_document(&self, name: &str, content: &[u8]) -> Result { + info!("Uploading new document: {}", name); + // Generate a unique ID let id = Uuid::new_v4().to_string(); + debug!("Generated document ID: {}", id); // Calculate document hash let mut hasher = Sha256::new(); hasher.update(content); let hash = format!("{:x}", hasher.finalize()); + debug!("Document hash: {}", hash); // Create document metadata let document = Document { @@ -73,12 +79,15 @@ impl DocumentService { // Store document metadata in Vault self.store_document_metadata(&document).await?; - println!("Document uploaded with ID: {}", id); + info!("Document uploaded with ID: {}", id); Ok(id) } // Store document metadata in Vault + #[instrument(skip(self, document), fields(document_id = %document.id))] async fn store_document_metadata(&self, document: &Document) -> Result<()> { + debug!("Storing document metadata for {}", document.id); + let url = format!("{}/v1/documents/data/docs/{}", self.vault_client.addr, document.id); @@ -104,18 +113,22 @@ impl DocumentService { match response.status() { StatusCode::OK | StatusCode::NO_CONTENT => { - println!("Successfully stored document metadata for {}", document.id); + info!("Successfully stored document metadata for {}", document.id); Ok(()) } status => { let error_text = response.text().await?; + error!("Failed to store document metadata: {} - {}", status, error_text); Err(anyhow::anyhow!("Failed to store document metadata: {} - {}", status, error_text)) } } } // Get document metadata + #[instrument(skip(self))] pub async fn get_document(&self, document_id: &str) -> Result { + debug!("Getting document metadata for {}", document_id); + let url = format!("{}/v1/documents/data/docs/{}", self.vault_client.addr, document_id); @@ -137,7 +150,10 @@ impl DocumentService { let status = match status_str { "pending" => DocumentStatus::Pending, "verified" => DocumentStatus::Verified, - _ => return Err(anyhow::anyhow!("Unknown status: {}", status_str)), + _ => { + error!("Unknown document status: {}", status_str); + return Err(anyhow::anyhow!("Unknown status: {}", status_str)); + } }; // Extract signatures @@ -160,17 +176,22 @@ impl DocumentService { signatures, }; + debug!("Retrieved document: {} with {} signatures", document.id, document.signatures.len()); Ok(document) } status => { let error_text = response.text().await?; + error!("Failed to get document: {} - {}", status, error_text); Err(anyhow::anyhow!("Failed to get document: {} - {}", status, error_text)) } } } // Sign a document with the user's key + #[instrument(skip(self, user_token), fields(document_id = %document_id, username = %username))] pub async fn sign_document(&self, document_id: &str, username: &str, user_token: &str) -> Result<()> { + info!("Signing document {} by user {}", document_id, username); + // Get document metadata let document = self.get_document(document_id).await?; @@ -200,6 +221,8 @@ impl DocumentService { .context("Failed to extract signature")? .to_string(); + debug!("Generated signature for document {}", document_id); + // Update document with signature self.add_signature(document_id, username, &signature).await?; @@ -209,18 +232,22 @@ impl DocumentService { // Check if document now has enough signatures self.update_document_status(document_id).await?; - println!("Document {} signed by {}", document_id, username); + info!("Document {} signed by {}", document_id, username); Ok(()) } status => { let error_text = response.text().await?; + error!("Failed to sign document: {} - {}", status, error_text); Err(anyhow::anyhow!("Failed to sign document: {} - {}", status, error_text)) } } } // Add a signature to a document + #[instrument(skip(self, signature), fields(document_id = %document_id, username = %username))] async fn add_signature(&self, document_id: &str, username: &str, signature: &str) -> Result<()> { + debug!("Adding signature from {} to document {}", username, document_id); + // Get current document let mut document = self.get_document(document_id).await?; @@ -230,16 +257,20 @@ impl DocumentService { // Store updated document self.store_document_metadata(&document).await?; + debug!("Added signature from {} to document {}", username, document_id); Ok(()) } // Record department signature + #[instrument(skip(self), fields(document_id = %document_id, department = ?user.department, username = %user.username))] async fn record_department_signature(&self, document_id: &str, user: &User) -> Result<()> { let dept_str = match user.department { Department::Legal => "legal", Department::Finance => "finance", }; + debug!("Recording {} department signature for document {}", dept_str, document_id); + let url = format!("{}/v1/documents/data/dept/{}/signatures/{}", self.vault_client.addr, dept_str, document_id); @@ -287,18 +318,22 @@ impl DocumentService { match response.status() { StatusCode::OK | StatusCode::NO_CONTENT => { - println!("Recorded signature for {} in {} department", user.username, dept_str); + info!("Recorded signature for {} in {} department", user.username, dept_str); Ok(()) } status => { let error_text = response.text().await?; + error!("Failed to record department signature: {} - {}", status, error_text); Err(anyhow::anyhow!("Failed to record department signature: {} - {}", status, error_text)) } } } // Update document status if it has enough signatures + #[instrument(skip(self))] async fn update_document_status(&self, document_id: &str) -> Result<()> { + debug!("Checking if document {} has enough signatures", document_id); + // Verify signatures let verification = self.verify_document_signatures(document_id).await?; @@ -312,14 +347,26 @@ impl DocumentService { // Store updated document self.store_document_metadata(&document).await?; - println!("Document {} marked as verified", document_id); + info!("Document {} marked as verified", document_id); + } else { + debug!( + "Document {} not yet verified. Has {}/{} signatures ({} legal, {} finance)", + document_id, + verification.signatures_count, + verification.required_signatures, + verification.legal_signatures, + verification.required_finance + ); } Ok(()) } // Verify document signatures + #[instrument(skip(self))] pub async fn verify_document_signatures(&self, document_id: &str) -> Result { + info!("Verifying signatures for document {}", document_id); + // Get document let document = self.get_document(document_id).await?; @@ -337,6 +384,7 @@ impl DocumentService { StatusCode::OK => response.json::().await?, status => { let error_text = response.text().await?; + error!("Failed to get signing requirements: {} - {}", status, error_text); return Err(anyhow::anyhow!("Failed to get signing requirements: {} - {}", status, error_text)); } }; @@ -384,11 +432,26 @@ impl DocumentService { required_finance, }; + info!( + "Verification result for document {}: verified={}, signatures={}/{}, legal={}/{}, finance={}/{}", + document_id, + verification.is_verified, + verification.signatures_count, + verification.required_signatures, + verification.legal_signatures, + verification.required_legal, + verification.finance_signatures, + verification.required_finance + ); + Ok(verification) } // Get department signatures for a document + #[instrument(skip(self))] async fn get_department_signatures(&self, document_id: &str, department: &str) -> Result> { + debug!("Getting {} department signatures for document {}", department, document_id); + let url = format!("{}/v1/documents/data/dept/{}/signatures/{}", self.vault_client.addr, department, document_id); @@ -414,6 +477,9 @@ impl DocumentService { } } + debug!("Found {} signatures for {} department on document {}", + signatures.len(), department, document_id); + Ok(signatures) } } diff --git a/src/main.rs b/src/main.rs index 1a487ed..e4eeed6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,8 @@ use std::{ time::Duration, }; use tokio::time::sleep; +use tracing::{info, warn, error, debug, instrument}; +use tracing_subscriber::{fmt, EnvFilter}; // Import our library use vault_hier::start_api; @@ -52,7 +54,7 @@ fn save_credentials(response: &InitResponse, file_path: &str) -> Result<()> { 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); + info!("Credentials saved to JSON file: {}", file_path); return Ok(()); } @@ -69,13 +71,14 @@ fn save_credentials(response: &InitResponse, file_path: &str) -> Result<()> { writeln!(file)?; writeln!(file, "Root Token: {}", response.root_token)?; - println!("Credentials saved to {}", file_path); + info!("Credentials saved to {}", file_path); Ok(()) } // Wait for Vault to become available +#[instrument(skip(addr))] async fn wait_for_vault(addr: &str) -> Result<()> { - println!("Waiting for Vault to be ready..."); + info!("Waiting for Vault to be ready..."); let client = Client::new(); @@ -87,30 +90,32 @@ async fn wait_for_vault(addr: &str) -> Result<()> { let status = response.status().as_u16(); // Accept any of these status codes as "available" if matches!(status, 200 | 429 | 472 | 473 | 501 | 503) { - println!("Vault is available! Status code: {}", status); + info!("Vault is available! Status code: {}", status); return Ok(()); } - println!("Vault returned unexpected status code: {}", status); + debug!("Vault returned unexpected status code: {}", status); }, Err(e) => { - println!("Error connecting to Vault: {}", e); + debug!("Error connecting to Vault: {}", e); } } if i == 30 { + error!("Timed out waiting for Vault to become available"); return Err(anyhow::anyhow!("Timed out waiting for Vault to become available")); } - println!("Vault is unavailable - sleeping (attempt {}/30)", i); + info!("Vault is unavailable - sleeping (attempt {}/30)", i); sleep(Duration::from_secs(2)).await; } Ok(()) } +#[instrument(skip(client, addr))] async fn check_init_status(client: &Client, addr: &str) -> Result { - println!("Checking if Vault is already initialized..."); + info!("Checking if Vault is already initialized..."); let response = client .get(format!("{}/v1/sys/init", addr)) @@ -128,8 +133,9 @@ async fn check_init_status(client: &Client, addr: &str) -> Result { Ok(false) } +#[instrument(skip(client, addr))] async fn check_seal_status(client: &Client, addr: &str) -> Result { - println!("Checking Vault seal status..."); + info!("Checking Vault seal status..."); let response = client .get(format!("{}/v1/sys/seal-status", addr)) @@ -138,24 +144,27 @@ async fn check_seal_status(client: &Client, addr: &str) -> Result().await?; - println!("Seal status: sealed={}, threshold={}, shares={}, progress={}", + info!("Seal status: sealed={}, threshold={}, shares={}, progress={}", status.sealed, status.t, status.n, status.progress); return Ok(status); } else { let error_text = response.text().await?; + error!("Failed to get seal status: {}", error_text); anyhow::bail!("Failed to get seal status: {}", error_text); } } +#[instrument(skip(client, addr))] async fn init_vault(client: &Client, addr: &str) -> Result { // First check if already initialized let initialized = check_init_status(client, addr).await?; if initialized { + error!("Vault is already initialized. Cannot re-initialize."); anyhow::bail!("Vault is already initialized. Cannot re-initialize."); } - println!("Initializing Vault..."); + info!("Initializing Vault..."); // Configure with 5 key shares and a threshold of 3 // This is a standard production configuration, requiring 3 out of 5 keys to unseal @@ -173,32 +182,39 @@ async fn init_vault(client: &Client, addr: &str) -> Result { match response.status() { StatusCode::OK => { let init_response = response.json::().await?; - println!("Vault initialized successfully!"); + info!("Vault initialized successfully!"); Ok(init_response) } status => { let error_text = response.text().await?; + error!("Failed to initialize Vault: {} - {}", status, error_text); anyhow::bail!("Failed to initialize Vault: {} - {}", status, error_text); } } } +#[instrument(skip(client, addr, unseal_keys))] async fn unseal_vault(client: &Client, addr: &str, unseal_keys: &[String]) -> Result<()> { // First check the current seal status let mut seal_status = check_seal_status(client, addr).await?; if !seal_status.sealed { - println!("Vault is already unsealed!"); + info!("Vault is already unsealed!"); return Ok(()); } - println!("Unsealing Vault..."); + info!("Unsealing Vault..."); // We need to provide enough keys to meet the threshold // The threshold is in seal_status.t let required_keys = seal_status.t as usize; if unseal_keys.len() < required_keys { + error!( + "Not enough unseal keys provided. Need {} keys, but only have {}", + required_keys, + unseal_keys.len() + ); anyhow::bail!( "Not enough unseal keys provided. Need {} keys, but only have {}", required_keys, @@ -208,7 +224,7 @@ async fn unseal_vault(client: &Client, addr: &str, unseal_keys: &[String]) -> Re // Apply each key one at a time until unsealed for (i, key) in unseal_keys.iter().take(required_keys).enumerate() { - println!("Applying unseal key {}/{}...", i + 1, required_keys); + info!("Applying unseal key {}/{}...", i + 1, required_keys); let unseal_req = UnsealRequest { key: key.clone(), @@ -222,6 +238,7 @@ async fn unseal_vault(client: &Client, addr: &str, unseal_keys: &[String]) -> Re if !response.status().is_success() { let error_text = response.text().await?; + error!("Failed to apply unseal key: {}", error_text); anyhow::bail!("Failed to apply unseal key: {}", error_text); } @@ -229,13 +246,14 @@ async fn unseal_vault(client: &Client, addr: &str, unseal_keys: &[String]) -> Re seal_status = check_seal_status(client, addr).await?; if !seal_status.sealed { - println!("Vault unsealed successfully after applying {} keys!", i + 1); + info!("Vault unsealed successfully after applying {} keys!", i + 1); return Ok(()); } } // If we get here, we've applied all keys but Vault is still sealed if seal_status.sealed { + error!("Applied all available unseal keys, but Vault is still sealed"); anyhow::bail!("Applied all available unseal keys, but Vault is still sealed"); } @@ -244,6 +262,12 @@ async fn unseal_vault(client: &Client, addr: &str, unseal_keys: &[String]) -> Re #[tokio::main] async fn main() -> Result<()> { + // Initialize tracing + fmt() + .with_env_filter(EnvFilter::from_default_env().add_directive("vault_hier=info".parse()?)) + .with_target(false) + .init(); + // Get Vault address from env var or use default let vault_addr = env::var("VAULT_ADDR").unwrap_or_else(|_| "http://127.0.0.1:8200".to_string()); let client = Client::new(); @@ -254,8 +278,8 @@ async fn main() -> Result<()> { .parse::() .unwrap_or(3000); - println!("Vault address: {}", vault_addr); - println!("Connecting to Vault at: {}", vault_addr); + info!("Vault address: {}", vault_addr); + info!("Connecting to Vault at: {}", vault_addr); // Wait for Vault to be available wait_for_vault(&vault_addr).await?; @@ -266,10 +290,10 @@ async fn main() -> Result<()> { Ok(response) => { if response.status().is_success() { let status_text = response.text().await?; - println!("Vault status: {}", status_text); + info!("Vault status: {}", status_text); } }, - Err(e) => println!("Error getting Vault status: {}", e), + Err(e) => warn!("Error getting Vault status: {}", e), } // First check if Vault is already initialized @@ -277,44 +301,44 @@ async fn main() -> Result<()> { let mut root_token = String::new(); if initialized { - println!("Vault is already initialized."); + info!("Vault is already initialized."); // Check if Vault is sealed let seal_status = check_seal_status(&client, &vault_addr).await?; if seal_status.sealed { - println!("Vault is sealed. Looking for unseal keys..."); + info!("Vault is sealed. Looking for unseal keys..."); // Try to load unseal keys from environment variables let mut unseal_keys = Vec::new(); for i in 1..=5 { match env::var(format!("VAULT_UNSEAL_KEY_{}", i)) { Ok(key) => { - println!("Found unseal key {} from environment", i); + info!("Found unseal key {} from environment", i); unseal_keys.push(key); }, Err(_) => { - println!("Unseal key {} not found in environment", i); + debug!("Unseal key {} not found in environment", i); } } } // If we have unseal keys, try to unseal if !unseal_keys.is_empty() { - println!("Found {} unseal keys. Attempting to unseal...", unseal_keys.len()); + info!("Found {} unseal keys. Attempting to unseal...", unseal_keys.len()); unseal_vault(&client, &vault_addr, &unseal_keys).await?; } else { - println!("No unseal keys found. Vault remains sealed."); - println!("To unseal, set VAULT_UNSEAL_KEY_1, VAULT_UNSEAL_KEY_2, etc. environment variables."); + warn!("No unseal keys found. Vault remains sealed."); + info!("To unseal, set VAULT_UNSEAL_KEY_1, VAULT_UNSEAL_KEY_2, etc. environment variables."); } } else { - println!("Vault is already unsealed."); + info!("Vault is already unsealed."); } // Try to load root token from environment or credentials file match env::var("VAULT_TOKEN") { Ok(token) => { - println!("Found root token from environment"); + info!("Found root token from environment"); root_token = token; }, Err(_) => { @@ -322,7 +346,7 @@ async fn main() -> Result<()> { if let Ok(contents) = std::fs::read_to_string("vault-credentials.json") { if let Ok(creds) = serde_json::from_str::(&contents) { if let Some(token) = creds["root_token"].as_str() { - println!("Found root token from credentials file"); + info!("Found root token from credentials file"); root_token = token.to_string(); } } @@ -331,47 +355,48 @@ async fn main() -> Result<()> { } if root_token.is_empty() { + error!("Unable to find root token. Please set VAULT_TOKEN environment variable or provide vault-credentials.json file."); anyhow::bail!("Unable to find root token. Please set VAULT_TOKEN environment variable or provide vault-credentials.json file."); } } else { // Initialize Vault - println!("Vault is not initialized. Proceeding with initialization..."); + info!("Vault is not initialized. Proceeding with initialization..."); let init_response = init_vault(&client, &vault_addr).await?; // Save credentials to files - println!("Saving credentials to files..."); + info!("Saving credentials to files..."); let current_dir = std::env::current_dir().context("Failed to get current directory")?; // Save as JSON (new format) 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()); + info!("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()); + info!("Text credentials saved to: {}", text_path.display()); // Also save to /app/data as a backup for Docker volume mounting if let Ok(()) = std::fs::create_dir_all("/app/data") { let docker_json_path = "/app/data/vault-credentials.json"; save_credentials(&init_response, docker_json_path)?; - println!("Backup JSON credentials saved to Docker volume at: {}", docker_json_path); + info!("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); + info!("Backup text credentials saved to Docker volume at: {}", docker_text_path); } - println!("========================================="); - println!("IMPORTANT: SAVE THESE CREDENTIALS SECURELY"); - println!("========================================="); - println!("Root Token: {}", init_response.root_token); - println!("Unseal Keys (first 3 of 5 needed to unseal):"); + info!("========================================="); + info!("IMPORTANT: SAVE THESE CREDENTIALS SECURELY"); + info!("========================================="); + info!("Root Token: {}", init_response.root_token); + info!("Unseal Keys (first 3 of 5 needed to unseal):"); for (i, key) in init_response.keys_base64.iter().enumerate() { - println!("Key {}: {}", i + 1, key); + info!("Key {}: {}", i + 1, key); } - println!("========================================="); + info!("========================================="); // Unseal Vault using the first three keys let unseal_keys = init_response.keys_base64.iter() @@ -381,7 +406,7 @@ async fn main() -> Result<()> { unseal_vault(&client, &vault_addr, &unseal_keys).await?; - println!("Vault initialization and unseal complete!"); + info!("Vault initialization and unseal complete!"); // Set root token root_token = init_response.root_token; @@ -390,11 +415,11 @@ async fn main() -> Result<()> { // Look for any existing credentials and copy them to the mounted volume 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..."); + info!("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), + Ok(_) => info!("JSON credentials saved to Docker volume"), + Err(e) => warn!("Failed to copy JSON credentials: {}", e), } } } @@ -402,23 +427,23 @@ async fn main() -> Result<()> { 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..."); + info!("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), + Ok(_) => info!("Text credentials saved to Docker volume"), + Err(e) => warn!("Failed to copy text credentials: {}", e), } } } } - println!("Vault setup complete!"); - println!("Starting hierarchical document signing API..."); + info!("Vault setup complete!"); + info!("Starting hierarchical document signing API..."); // Start the hierarchical signing API start_api(&vault_addr, &root_token, api_port).await?; - println!("API server shutdown. Exiting."); + info!("API server shutdown. Exiting."); Ok(()) } diff --git a/src/vault_setup.rs b/src/vault_setup.rs index 8caf4d3..c603d31 100644 --- a/src/vault_setup.rs +++ b/src/vault_setup.rs @@ -2,6 +2,7 @@ use anyhow::{Context, Result}; use reqwest::{Client, StatusCode}; use serde::{Deserialize, Serialize}; use serde_json::json; +use tracing::{info, error, debug, instrument}; // Department types for organizational structure #[derive(Debug, Clone, Serialize, Deserialize)] @@ -35,8 +36,9 @@ impl VaultClient { } // Enable required secrets engines + #[instrument(skip(self))] pub async fn setup_secrets_engines(&self) -> Result<()> { - println!("Setting up Vault secrets engines..."); + info!("Setting up Vault secrets engines..."); // Enable Transit for document signing self.enable_secrets_engine("transit", "transit").await?; @@ -47,13 +49,14 @@ impl VaultClient { // Enable userpass for authentication self.enable_auth_method("userpass").await?; - println!("Secrets engines setup complete!"); + info!("Secrets engines setup complete!"); Ok(()) } // Enable a secrets engine + #[instrument(skip(self))] async fn enable_secrets_engine(&self, engine_type: &str, path: &str) -> Result<()> { - println!("Enabling {} secrets engine at {}", engine_type, path); + info!("Enabling {} secrets engine at {}", engine_type, path); let url = format!("{}/v1/sys/mounts/{}", self.addr, path); let payload = json!({ @@ -69,29 +72,32 @@ impl VaultClient { match response.status() { StatusCode::NO_CONTENT | StatusCode::OK => { - println!("Successfully enabled {} engine at {}", engine_type, path); + info!("Successfully enabled {} engine at {}", engine_type, path); Ok(()) } StatusCode::BAD_REQUEST => { // Check if already exists let error_text = response.text().await?; if error_text.contains("path is already in use") { - println!("Secrets engine already enabled at {}", path); + info!("Secrets engine already enabled at {}", path); Ok(()) } else { + error!("Failed to enable secrets engine: {}", error_text); Err(anyhow::anyhow!("Failed to enable secrets engine: {}", error_text)) } } status => { let error_text = response.text().await?; + error!("Failed to enable secrets engine: {} - {}", status, error_text); Err(anyhow::anyhow!("Failed to enable secrets engine: {} - {}", status, error_text)) } } } // Enable an auth method + #[instrument(skip(self))] async fn enable_auth_method(&self, method: &str) -> Result<()> { - println!("Enabling {} auth method", method); + info!("Enabling {} auth method", method); let url = format!("{}/v1/sys/auth/{}", self.addr, method); let payload = json!({ @@ -107,29 +113,32 @@ impl VaultClient { match response.status() { StatusCode::NO_CONTENT | StatusCode::OK => { - println!("Successfully enabled {} auth method", method); + info!("Successfully enabled {} auth method", method); Ok(()) } StatusCode::BAD_REQUEST => { // Check if already exists let error_text = response.text().await?; if error_text.contains("path is already in use") { - println!("Auth method already enabled at {}", method); + info!("Auth method already enabled at {}", method); Ok(()) } else { + error!("Failed to enable auth method: {}", error_text); Err(anyhow::anyhow!("Failed to enable auth method: {}", error_text)) } } status => { let error_text = response.text().await?; + error!("Failed to enable auth method: {} - {}", status, error_text); Err(anyhow::anyhow!("Failed to enable auth method: {} - {}", status, error_text)) } } } // Create a new user in Vault and associate with department + #[instrument(skip(self, password))] pub async fn create_user(&self, username: &str, password: &str, department: Department) -> Result<()> { - println!("Creating user {} in department {:?}", username, department); + info!("Creating user {} in department {:?}", username, department); // Step 1: Create a policy for the user let policy_name = format!("{}-policy", username); @@ -151,7 +160,7 @@ impl VaultClient { match response.status() { StatusCode::NO_CONTENT | StatusCode::OK => { - println!("Successfully created user {}", username); + info!("Successfully created user {}", username); // Step 3: Create a signing key for the user self.create_signing_key(username).await?; @@ -163,12 +172,14 @@ impl VaultClient { } status => { let error_text = response.text().await?; + error!("Failed to create user: {} - {}", status, error_text); Err(anyhow::anyhow!("Failed to create user: {} - {}", status, error_text)) } } } // Create a signing policy for a user based on their department + #[instrument(skip(self))] async fn create_signing_policy(&self, policy_name: &str, department: Department) -> Result<()> { let dept_name = match department { Department::Legal => "legal", @@ -212,17 +223,19 @@ impl VaultClient { match response.status() { StatusCode::NO_CONTENT | StatusCode::OK => { - println!("Successfully created policy {}", policy_name); + info!("Successfully created policy {}", policy_name); Ok(()) } status => { let error_text = response.text().await?; + error!("Failed to create policy: {} - {}", status, error_text); Err(anyhow::anyhow!("Failed to create policy: {} - {}", status, error_text)) } } } // Create a signing key for a user in the Transit engine + #[instrument(skip(self))] async fn create_signing_key(&self, username: &str) -> Result<()> { let url = format!("{}/v1/transit/keys/{}", self.addr, username); let payload = json!({ @@ -238,17 +251,19 @@ impl VaultClient { match response.status() { StatusCode::NO_CONTENT | StatusCode::OK => { - println!("Successfully created signing key for {}", username); + info!("Successfully created signing key for {}", username); Ok(()) } status => { let error_text = response.text().await?; + error!("Failed to create signing key: {} - {}", status, error_text); Err(anyhow::anyhow!("Failed to create signing key: {} - {}", status, error_text)) } } } // Store user metadata in the KV store + #[instrument(skip(self))] async fn store_user_metadata(&self, username: &str, department: Department) -> Result<()> { let dept_str = match department { Department::Legal => "legal", @@ -272,22 +287,27 @@ impl VaultClient { match response.status() { StatusCode::NO_CONTENT | StatusCode::OK => { - println!("Successfully stored metadata for user {}", username); + info!("Successfully stored metadata for user {}", username); Ok(()) } status => { let error_text = response.text().await?; + error!("Failed to store user metadata: {} - {}", status, error_text); Err(anyhow::anyhow!("Failed to store user metadata: {} - {}", status, error_text)) } } } // Create 10 users with departmental hierarchy - 5 in each department + #[instrument(skip(self))] pub async fn setup_hierarchical_users(&self) -> Result<()> { + info!("Setting up hierarchical user structure"); + // Create 5 users in Legal department for i in 1..=5 { let username = format!("legal{}", i); let password = format!("legal{}pass", i); + debug!(username, "Creating Legal department user"); self.create_user(&username, &password, Department::Legal).await?; } @@ -295,18 +315,22 @@ impl VaultClient { for i in 1..=5 { let username = format!("finance{}", i); let password = format!("finance{}pass", i); + debug!(username, "Creating Finance department user"); self.create_user(&username, &password, Department::Finance).await?; } // Setup document signing requirements self.setup_signing_requirements().await?; - println!("Successfully created 10 users in hierarchical structure!"); + info!("Successfully created 10 users in hierarchical structure!"); Ok(()) } // Configure document signing requirements + #[instrument(skip(self))] async fn setup_signing_requirements(&self) -> Result<()> { + info!("Setting up document signing requirements"); + let url = format!("{}/v1/documents/data/config/signing_requirements", self.addr); let payload = json!({ "data": { @@ -336,18 +360,22 @@ impl VaultClient { match response.status() { StatusCode::NO_CONTENT | StatusCode::OK => { - println!("Successfully configured signing requirements"); + info!("Successfully configured signing requirements"); Ok(()) } status => { let error_text = response.text().await?; + error!("Failed to configure signing requirements: {} - {}", status, error_text); Err(anyhow::anyhow!("Failed to configure signing requirements: {} - {}", status, error_text)) } } } // Login a user and get their token + #[instrument(skip(self, password))] pub async fn login_user(&self, username: &str, password: &str) -> Result { + info!("Attempting user login: {}", username); + let url = format!("{}/v1/auth/userpass/login/{}", self.addr, username); let payload = json!({ "password": password, @@ -367,18 +395,22 @@ impl VaultClient { .context("Failed to extract client token")? .to_string(); - println!("User {} successfully logged in", username); + info!("User {} successfully logged in", username); Ok(token) } status => { let error_text = response.text().await?; + error!("Failed to login: {} - {}", status, error_text); Err(anyhow::anyhow!("Failed to login: {} - {}", status, error_text)) } } } // Get user info including department + #[instrument(skip(self))] pub async fn get_user_info(&self, username: &str) -> Result { + debug!("Getting user info for: {}", username); + let url = format!("{}/v1/documents/data/users/{}", self.addr, username); let response = self.client @@ -397,9 +429,13 @@ impl VaultClient { let department = match department_str { "legal" => Department::Legal, "finance" => Department::Finance, - _ => return Err(anyhow::anyhow!("Unknown department: {}", department_str)), + _ => { + error!("Unknown department: {}", department_str); + return Err(anyhow::anyhow!("Unknown department: {}", department_str)); + } }; + debug!("Retrieved user info for {} in {:?} department", username, department); Ok(User { username: username.to_string(), department, @@ -407,6 +443,7 @@ impl VaultClient { } status => { let error_text = response.text().await?; + error!("Failed to get user info: {} - {}", status, error_text); Err(anyhow::anyhow!("Failed to get user info: {} - {}", status, error_text)) } }