feat: integrate tracing for structured logging

- Added `tracing` and `tracing-subscriber` for improved logging, replacing `println` statements with `info`, `debug`, `warn`, and `error`.
- Annotated key methods with `#[instrument]` for better tracing of function calls and arguments.
- Configured logging initialization in `main.rs` with `EnvFilter` to control log verbosity.
This commit is contained in:
Harald Hoyer 2025-03-20 14:59:22 +01:00
parent f11b83ddf4
commit 8f28cc1af2
5 changed files with 243 additions and 81 deletions

View file

@ -13,6 +13,5 @@ axum = { version = "0.6.18", features = ["multipart"] }
uuid = { version = "1.3.0", features = ["v4", "serde"] } uuid = { version = "1.3.0", features = ["v4", "serde"] }
sha2 = "0.10.6" sha2 = "0.10.6"
base64 = "0.21.0" base64 = "0.21.0"
tower = "0.4.13" tracing = "0.1.37"
tower-http = { version = "0.4.0", features = ["cors"] } tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
futures = "0.3.28"

View file

@ -10,6 +10,7 @@ use axum::{
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::sync::Arc; use std::sync::Arc;
use tokio::net::TcpListener; use tokio::net::TcpListener;
use tracing::{info, error, debug, instrument};
use crate::document_service::{Document, DocumentService, SignatureVerification}; use crate::document_service::{Document, DocumentService, SignatureVerification};
use crate::vault_setup::VaultClient; use crate::vault_setup::VaultClient;
@ -48,6 +49,7 @@ pub struct SignDocumentRequest {
// API response implementations // API response implementations
impl IntoResponse for ApiError { impl IntoResponse for ApiError {
fn into_response(self) -> Response { fn into_response(self) -> Response {
error!("API error: {}", self.0);
( (
StatusCode::INTERNAL_SERVER_ERROR, StatusCode::INTERNAL_SERVER_ERROR,
format!("Error: {}", self.0), format!("Error: {}", self.0),
@ -66,12 +68,13 @@ where
} }
// Start the API server // Start the API server
#[instrument(skip(vault_addr, root_token))]
pub async fn start_api( pub async fn start_api(
vault_addr: &str, vault_addr: &str,
root_token: &str, root_token: &str,
api_port: u16, api_port: u16,
) -> Result<()> { ) -> Result<()> {
println!("Starting API server on port {}...", api_port); info!("Starting API server on port {}...", api_port);
// Initialize Vault client // Initialize Vault client
let vault_client = VaultClient::new(vault_addr, root_token); 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)) .route("/api/documents/:id/verify", get(verify_document))
.with_state(state); .with_state(state);
info!("API routes configured");
// Start server // Start server
let listener = TcpListener::bind(format!("0.0.0.0:{}", api_port)).await?; 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 // Get the socket address
let addr = listener.local_addr()?; let addr = listener.local_addr()?;
// Bind and serve // Bind and serve
info!("Serving API at {}", addr);
Server::bind(&addr) Server::bind(&addr)
.serve(app.into_make_service()) .serve(app.into_make_service())
.await?; .await?;
@ -117,27 +123,36 @@ pub async fn start_api(
} }
// Health check endpoint // Health check endpoint
#[instrument]
async fn health_check() -> &'static str { async fn health_check() -> &'static str {
debug!("Health check endpoint called");
"OK" "OK"
} }
// Login endpoint // Login endpoint
#[instrument(skip(state, request), fields(username = %request.username))]
async fn login( async fn login(
State(state): State<ApiState>, State(state): State<ApiState>,
Json(request): Json<LoginRequest>, Json(request): Json<LoginRequest>,
) -> Result<Json<LoginResponse>, ApiError> { ) -> Result<Json<LoginResponse>, ApiError> {
info!("Login attempt for user: {}", request.username);
let token = state.vault_client let token = state.vault_client
.login_user(&request.username, &request.password) .login_user(&request.username, &request.password)
.await?; .await?;
info!("User {} successfully authenticated", request.username);
Ok(Json(LoginResponse { token })) Ok(Json(LoginResponse { token }))
} }
// Upload document endpoint // Upload document endpoint
#[instrument(skip(state, multipart))]
async fn upload_document( async fn upload_document(
State(state): State<ApiState>, State(state): State<ApiState>,
mut multipart: Multipart, mut multipart: Multipart,
) -> Result<Json<Document>, ApiError> { ) -> Result<Json<Document>, ApiError> {
info!("Document upload request received");
let mut document_name = String::new(); let mut document_name = String::new();
let mut document_content = Vec::new(); let mut document_content = Vec::new();
@ -147,12 +162,15 @@ async fn upload_document(
if name == "name" { if name == "name" {
document_name = field.text().await?; document_name = field.text().await?;
debug!("Received document name: {}", document_name);
} else if name == "file" { } else if name == "file" {
document_content = field.bytes().await?.to_vec(); document_content = field.bytes().await?.to_vec();
debug!("Received document content: {} bytes", document_content.len());
} }
} }
if document_name.is_empty() || document_content.is_empty() { 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()); return Err(anyhow::anyhow!("Missing document name or content").into());
} }
@ -166,27 +184,37 @@ async fn upload_document(
.get_document(&document_id) .get_document(&document_id)
.await?; .await?;
info!("Document uploaded successfully with ID: {}", document_id);
Ok(Json(document)) Ok(Json(document))
} }
// Get document endpoint // Get document endpoint
#[instrument(skip(state))]
async fn get_document( async fn get_document(
State(state): State<ApiState>, State(state): State<ApiState>,
Path(document_id): Path<String>, Path(document_id): Path<String>,
) -> Result<Json<Document>, ApiError> { ) -> Result<Json<Document>, ApiError> {
info!("Fetching document: {}", document_id);
let document = state.document_service let document = state.document_service
.get_document(&document_id) .get_document(&document_id)
.await?; .await?;
debug!("Retrieved document {} with {} signatures",
document.id, document.signatures.len());
Ok(Json(document)) Ok(Json(document))
} }
// Sign document endpoint // Sign document endpoint
#[instrument(skip(state, request), fields(document_id = %document_id, username = %request.username))]
async fn sign_document( async fn sign_document(
State(state): State<ApiState>, State(state): State<ApiState>,
Path(document_id): Path<String>, Path(document_id): Path<String>,
Json(request): Json<SignDocumentRequest>, Json(request): Json<SignDocumentRequest>,
) -> Result<Json<Document>, ApiError> { ) -> Result<Json<Document>, ApiError> {
info!("Signing request for document {} by user {}", document_id, request.username);
state.document_service state.document_service
.sign_document(&document_id, &request.username, &request.token) .sign_document(&document_id, &request.username, &request.token)
.await?; .await?;
@ -195,17 +223,24 @@ async fn sign_document(
.get_document(&document_id) .get_document(&document_id)
.await?; .await?;
info!("Document {} successfully signed by {}", document_id, request.username);
Ok(Json(document)) Ok(Json(document))
} }
// Verify document endpoint // Verify document endpoint
#[instrument(skip(state))]
async fn verify_document( async fn verify_document(
State(state): State<ApiState>, State(state): State<ApiState>,
Path(document_id): Path<String>, Path(document_id): Path<String>,
) -> Result<Json<SignatureVerification>, ApiError> { ) -> Result<Json<SignatureVerification>, ApiError> {
info!("Verifying document signatures: {}", document_id);
let verification = state.document_service let verification = state.document_service
.verify_document_signatures(&document_id) .verify_document_signatures(&document_id)
.await?; .await?;
info!("Document {} verification result: {}",
document_id, if verification.is_verified { "VERIFIED" } else { "PENDING" });
Ok(Json(verification)) Ok(Json(verification))
} }

View file

@ -6,6 +6,7 @@ use sha2::{Sha256, Digest};
use std::collections::HashMap; use std::collections::HashMap;
use uuid::Uuid; use uuid::Uuid;
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64}; use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
use tracing::{info, error, debug, instrument};
use crate::vault_setup::{Department, User, VaultClient}; use crate::vault_setup::{Department, User, VaultClient};
@ -52,14 +53,19 @@ impl DocumentService {
} }
// Upload a new document and store its metadata // 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<String> { pub async fn upload_document(&self, name: &str, content: &[u8]) -> Result<String> {
info!("Uploading new document: {}", name);
// Generate a unique ID // Generate a unique ID
let id = Uuid::new_v4().to_string(); let id = Uuid::new_v4().to_string();
debug!("Generated document ID: {}", id);
// Calculate document hash // Calculate document hash
let mut hasher = Sha256::new(); let mut hasher = Sha256::new();
hasher.update(content); hasher.update(content);
let hash = format!("{:x}", hasher.finalize()); let hash = format!("{:x}", hasher.finalize());
debug!("Document hash: {}", hash);
// Create document metadata // Create document metadata
let document = Document { let document = Document {
@ -73,12 +79,15 @@ impl DocumentService {
// Store document metadata in Vault // Store document metadata in Vault
self.store_document_metadata(&document).await?; self.store_document_metadata(&document).await?;
println!("Document uploaded with ID: {}", id); info!("Document uploaded with ID: {}", id);
Ok(id) Ok(id)
} }
// Store document metadata in Vault // Store document metadata in Vault
#[instrument(skip(self, document), fields(document_id = %document.id))]
async fn store_document_metadata(&self, document: &Document) -> Result<()> { async fn store_document_metadata(&self, document: &Document) -> Result<()> {
debug!("Storing document metadata for {}", document.id);
let url = format!("{}/v1/documents/data/docs/{}", let url = format!("{}/v1/documents/data/docs/{}",
self.vault_client.addr, document.id); self.vault_client.addr, document.id);
@ -104,18 +113,22 @@ impl DocumentService {
match response.status() { match response.status() {
StatusCode::OK | StatusCode::NO_CONTENT => { StatusCode::OK | StatusCode::NO_CONTENT => {
println!("Successfully stored document metadata for {}", document.id); info!("Successfully stored document metadata for {}", document.id);
Ok(()) Ok(())
} }
status => { status => {
let error_text = response.text().await?; 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)) Err(anyhow::anyhow!("Failed to store document metadata: {} - {}", status, error_text))
} }
} }
} }
// Get document metadata // Get document metadata
#[instrument(skip(self))]
pub async fn get_document(&self, document_id: &str) -> Result<Document> { pub async fn get_document(&self, document_id: &str) -> Result<Document> {
debug!("Getting document metadata for {}", document_id);
let url = format!("{}/v1/documents/data/docs/{}", let url = format!("{}/v1/documents/data/docs/{}",
self.vault_client.addr, document_id); self.vault_client.addr, document_id);
@ -137,7 +150,10 @@ impl DocumentService {
let status = match status_str { let status = match status_str {
"pending" => DocumentStatus::Pending, "pending" => DocumentStatus::Pending,
"verified" => DocumentStatus::Verified, "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 // Extract signatures
@ -160,17 +176,22 @@ impl DocumentService {
signatures, signatures,
}; };
debug!("Retrieved document: {} with {} signatures", document.id, document.signatures.len());
Ok(document) Ok(document)
} }
status => { status => {
let error_text = response.text().await?; let error_text = response.text().await?;
error!("Failed to get document: {} - {}", status, error_text);
Err(anyhow::anyhow!("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 // 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<()> { 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 // Get document metadata
let document = self.get_document(document_id).await?; let document = self.get_document(document_id).await?;
@ -200,6 +221,8 @@ impl DocumentService {
.context("Failed to extract signature")? .context("Failed to extract signature")?
.to_string(); .to_string();
debug!("Generated signature for document {}", document_id);
// Update document with signature // Update document with signature
self.add_signature(document_id, username, &signature).await?; self.add_signature(document_id, username, &signature).await?;
@ -209,18 +232,22 @@ impl DocumentService {
// Check if document now has enough signatures // Check if document now has enough signatures
self.update_document_status(document_id).await?; self.update_document_status(document_id).await?;
println!("Document {} signed by {}", document_id, username); info!("Document {} signed by {}", document_id, username);
Ok(()) Ok(())
} }
status => { status => {
let error_text = response.text().await?; let error_text = response.text().await?;
error!("Failed to sign document: {} - {}", status, error_text);
Err(anyhow::anyhow!("Failed to sign document: {} - {}", status, error_text)) Err(anyhow::anyhow!("Failed to sign document: {} - {}", status, error_text))
} }
} }
} }
// Add a signature to a document // 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<()> { 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 // Get current document
let mut document = self.get_document(document_id).await?; let mut document = self.get_document(document_id).await?;
@ -230,16 +257,20 @@ impl DocumentService {
// Store updated document // Store updated document
self.store_document_metadata(&document).await?; self.store_document_metadata(&document).await?;
debug!("Added signature from {} to document {}", username, document_id);
Ok(()) Ok(())
} }
// Record department signature // 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<()> { async fn record_department_signature(&self, document_id: &str, user: &User) -> Result<()> {
let dept_str = match user.department { let dept_str = match user.department {
Department::Legal => "legal", Department::Legal => "legal",
Department::Finance => "finance", Department::Finance => "finance",
}; };
debug!("Recording {} department signature for document {}", dept_str, document_id);
let url = format!("{}/v1/documents/data/dept/{}/signatures/{}", let url = format!("{}/v1/documents/data/dept/{}/signatures/{}",
self.vault_client.addr, dept_str, document_id); self.vault_client.addr, dept_str, document_id);
@ -287,18 +318,22 @@ impl DocumentService {
match response.status() { match response.status() {
StatusCode::OK | StatusCode::NO_CONTENT => { 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(()) Ok(())
} }
status => { status => {
let error_text = response.text().await?; 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)) Err(anyhow::anyhow!("Failed to record department signature: {} - {}", status, error_text))
} }
} }
} }
// Update document status if it has enough signatures // Update document status if it has enough signatures
#[instrument(skip(self))]
async fn update_document_status(&self, document_id: &str) -> Result<()> { async fn update_document_status(&self, document_id: &str) -> Result<()> {
debug!("Checking if document {} has enough signatures", document_id);
// Verify signatures // Verify signatures
let verification = self.verify_document_signatures(document_id).await?; let verification = self.verify_document_signatures(document_id).await?;
@ -312,14 +347,26 @@ impl DocumentService {
// Store updated document // Store updated document
self.store_document_metadata(&document).await?; 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(()) Ok(())
} }
// Verify document signatures // Verify document signatures
#[instrument(skip(self))]
pub async fn verify_document_signatures(&self, document_id: &str) -> Result<SignatureVerification> { pub async fn verify_document_signatures(&self, document_id: &str) -> Result<SignatureVerification> {
info!("Verifying signatures for document {}", document_id);
// Get document // Get document
let document = self.get_document(document_id).await?; let document = self.get_document(document_id).await?;
@ -337,6 +384,7 @@ impl DocumentService {
StatusCode::OK => response.json::<serde_json::Value>().await?, StatusCode::OK => response.json::<serde_json::Value>().await?,
status => { status => {
let error_text = response.text().await?; 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)); return Err(anyhow::anyhow!("Failed to get signing requirements: {} - {}", status, error_text));
} }
}; };
@ -384,11 +432,26 @@ impl DocumentService {
required_finance, 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) Ok(verification)
} }
// Get department signatures for a document // Get department signatures for a document
#[instrument(skip(self))]
async fn get_department_signatures(&self, document_id: &str, department: &str) -> Result<Vec<String>> { async fn get_department_signatures(&self, document_id: &str, department: &str) -> Result<Vec<String>> {
debug!("Getting {} department signatures for document {}", department, document_id);
let url = format!("{}/v1/documents/data/dept/{}/signatures/{}", let url = format!("{}/v1/documents/data/dept/{}/signatures/{}",
self.vault_client.addr, department, document_id); 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) Ok(signatures)
} }
} }

View file

@ -9,6 +9,8 @@ use std::{
time::Duration, time::Duration,
}; };
use tokio::time::sleep; use tokio::time::sleep;
use tracing::{info, warn, error, debug, instrument};
use tracing_subscriber::{fmt, EnvFilter};
// Import our library // Import our library
use vault_hier::start_api; 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))?; let mut file = File::create(Path::new(file_path))?;
file.write_all(serde_json::to_string_pretty(&json)?.as_bytes())?; 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(()); return Ok(());
} }
@ -69,13 +71,14 @@ fn save_credentials(response: &InitResponse, file_path: &str) -> Result<()> {
writeln!(file)?; writeln!(file)?;
writeln!(file, "Root Token: {}", response.root_token)?; writeln!(file, "Root Token: {}", response.root_token)?;
println!("Credentials saved to {}", file_path); info!("Credentials saved to {}", file_path);
Ok(()) Ok(())
} }
// Wait for Vault to become available // Wait for Vault to become available
#[instrument(skip(addr))]
async fn wait_for_vault(addr: &str) -> Result<()> { 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(); let client = Client::new();
@ -87,30 +90,32 @@ async fn wait_for_vault(addr: &str) -> Result<()> {
let status = response.status().as_u16(); let status = response.status().as_u16();
// Accept any of these status codes as "available" // Accept any of these status codes as "available"
if matches!(status, 200 | 429 | 472 | 473 | 501 | 503) { 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(()); return Ok(());
} }
println!("Vault returned unexpected status code: {}", status); debug!("Vault returned unexpected status code: {}", status);
}, },
Err(e) => { Err(e) => {
println!("Error connecting to Vault: {}", e); debug!("Error connecting to Vault: {}", e);
} }
} }
if i == 30 { if i == 30 {
error!("Timed out waiting for Vault to become available");
return Err(anyhow::anyhow!("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; sleep(Duration::from_secs(2)).await;
} }
Ok(()) Ok(())
} }
#[instrument(skip(client, addr))]
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..."); info!("Checking if Vault is already initialized...");
let response = client let response = client
.get(format!("{}/v1/sys/init", addr)) .get(format!("{}/v1/sys/init", addr))
@ -128,8 +133,9 @@ async fn check_init_status(client: &Client, addr: &str) -> Result<bool> {
Ok(false) Ok(false)
} }
#[instrument(skip(client, addr))]
async fn check_seal_status(client: &Client, addr: &str) -> Result<SealStatusResponse> { async fn check_seal_status(client: &Client, addr: &str) -> Result<SealStatusResponse> {
println!("Checking Vault seal status..."); info!("Checking Vault seal status...");
let response = client let response = client
.get(format!("{}/v1/sys/seal-status", addr)) .get(format!("{}/v1/sys/seal-status", addr))
@ -138,24 +144,27 @@ async fn check_seal_status(client: &Client, addr: &str) -> Result<SealStatusResp
if response.status().is_success() { if response.status().is_success() {
let status = response.json::<SealStatusResponse>().await?; let status = response.json::<SealStatusResponse>().await?;
println!("Seal status: sealed={}, threshold={}, shares={}, progress={}", info!("Seal status: sealed={}, threshold={}, shares={}, progress={}",
status.sealed, status.t, status.n, status.progress); status.sealed, status.t, status.n, status.progress);
return Ok(status); return Ok(status);
} else { } else {
let error_text = response.text().await?; let error_text = response.text().await?;
error!("Failed to get seal status: {}", error_text);
anyhow::bail!("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<InitResponse> { async fn init_vault(client: &Client, addr: &str) -> Result<InitResponse> {
// First check if already initialized // First check if already initialized
let initialized = check_init_status(client, addr).await?; let initialized = check_init_status(client, addr).await?;
if initialized { if initialized {
error!("Vault is already initialized. Cannot re-initialize.");
anyhow::bail!("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 // Configure with 5 key shares and a threshold of 3
// This is a standard production configuration, requiring 3 out of 5 keys to unseal // 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<InitResponse> {
match response.status() { match response.status() {
StatusCode::OK => { StatusCode::OK => {
let init_response = response.json::<InitResponse>().await?; let init_response = response.json::<InitResponse>().await?;
println!("Vault initialized successfully!"); info!("Vault initialized successfully!");
Ok(init_response) Ok(init_response)
} }
status => { status => {
let error_text = response.text().await?; let error_text = response.text().await?;
error!("Failed to initialize Vault: {} - {}", status, error_text);
anyhow::bail!("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<()> { async fn unseal_vault(client: &Client, addr: &str, unseal_keys: &[String]) -> Result<()> {
// First check the current seal status // First check the current seal status
let mut seal_status = check_seal_status(client, addr).await?; let mut seal_status = check_seal_status(client, addr).await?;
if !seal_status.sealed { if !seal_status.sealed {
println!("Vault is already unsealed!"); info!("Vault is already unsealed!");
return Ok(()); return Ok(());
} }
println!("Unsealing Vault..."); info!("Unsealing Vault...");
// We need to provide enough keys to meet the threshold // We need to provide enough keys to meet the threshold
// The threshold is in seal_status.t // The threshold is in seal_status.t
let required_keys = seal_status.t as usize; let required_keys = seal_status.t as usize;
if unseal_keys.len() < required_keys { if unseal_keys.len() < required_keys {
error!(
"Not enough unseal keys provided. Need {} keys, but only have {}",
required_keys,
unseal_keys.len()
);
anyhow::bail!( anyhow::bail!(
"Not enough unseal keys provided. Need {} keys, but only have {}", "Not enough unseal keys provided. Need {} keys, but only have {}",
required_keys, 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 // Apply each key one at a time until unsealed
for (i, key) in unseal_keys.iter().take(required_keys).enumerate() { 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 { let unseal_req = UnsealRequest {
key: key.clone(), key: key.clone(),
@ -222,6 +238,7 @@ async fn unseal_vault(client: &Client, addr: &str, unseal_keys: &[String]) -> Re
if !response.status().is_success() { if !response.status().is_success() {
let error_text = response.text().await?; let error_text = response.text().await?;
error!("Failed to apply unseal key: {}", error_text);
anyhow::bail!("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?; seal_status = check_seal_status(client, addr).await?;
if !seal_status.sealed { if !seal_status.sealed {
println!("Vault unsealed successfully after applying {} keys!", i + 1); info!("Vault unsealed successfully after applying {} keys!", i + 1);
return Ok(()); return Ok(());
} }
} }
// If we get here, we've applied all keys but Vault is still sealed // If we get here, we've applied all keys but Vault is still sealed
if seal_status.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"); 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] #[tokio::main]
async fn main() -> Result<()> { 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 // 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 vault_addr = env::var("VAULT_ADDR").unwrap_or_else(|_| "http://127.0.0.1:8200".to_string());
let client = Client::new(); let client = Client::new();
@ -254,8 +278,8 @@ async fn main() -> Result<()> {
.parse::<u16>() .parse::<u16>()
.unwrap_or(3000); .unwrap_or(3000);
println!("Vault address: {}", vault_addr); info!("Vault address: {}", vault_addr);
println!("Connecting to Vault at: {}", vault_addr); info!("Connecting to Vault at: {}", vault_addr);
// Wait for Vault to be available // Wait for Vault to be available
wait_for_vault(&vault_addr).await?; wait_for_vault(&vault_addr).await?;
@ -266,10 +290,10 @@ async fn main() -> Result<()> {
Ok(response) => { Ok(response) => {
if response.status().is_success() { if response.status().is_success() {
let status_text = response.text().await?; 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 // First check if Vault is already initialized
@ -277,44 +301,44 @@ async fn main() -> Result<()> {
let mut root_token = String::new(); let mut root_token = String::new();
if initialized { if initialized {
println!("Vault is already initialized."); info!("Vault is already initialized.");
// Check if Vault is sealed // Check if Vault is sealed
let seal_status = check_seal_status(&client, &vault_addr).await?; let seal_status = check_seal_status(&client, &vault_addr).await?;
if seal_status.sealed { 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 // Try to load unseal keys from environment variables
let mut unseal_keys = Vec::new(); let mut unseal_keys = Vec::new();
for i in 1..=5 { for i in 1..=5 {
match env::var(format!("VAULT_UNSEAL_KEY_{}", i)) { match env::var(format!("VAULT_UNSEAL_KEY_{}", i)) {
Ok(key) => { Ok(key) => {
println!("Found unseal key {} from environment", i); info!("Found unseal key {} from environment", i);
unseal_keys.push(key); unseal_keys.push(key);
}, },
Err(_) => { 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 we have unseal keys, try to unseal
if !unseal_keys.is_empty() { 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?; unseal_vault(&client, &vault_addr, &unseal_keys).await?;
} else { } else {
println!("No unseal keys found. Vault remains sealed."); warn!("No unseal keys found. Vault remains sealed.");
println!("To unseal, set VAULT_UNSEAL_KEY_1, VAULT_UNSEAL_KEY_2, etc. environment variables."); info!("To unseal, set VAULT_UNSEAL_KEY_1, VAULT_UNSEAL_KEY_2, etc. environment variables.");
} }
} else { } else {
println!("Vault is already unsealed."); info!("Vault is already unsealed.");
} }
// Try to load root token from environment or credentials file // Try to load root token from environment or credentials file
match env::var("VAULT_TOKEN") { match env::var("VAULT_TOKEN") {
Ok(token) => { Ok(token) => {
println!("Found root token from environment"); info!("Found root token from environment");
root_token = token; root_token = token;
}, },
Err(_) => { Err(_) => {
@ -322,7 +346,7 @@ async fn main() -> Result<()> {
if let Ok(contents) = std::fs::read_to_string("vault-credentials.json") { if let Ok(contents) = std::fs::read_to_string("vault-credentials.json") {
if let Ok(creds) = serde_json::from_str::<serde_json::Value>(&contents) { if let Ok(creds) = serde_json::from_str::<serde_json::Value>(&contents) {
if let Some(token) = creds["root_token"].as_str() { 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(); root_token = token.to_string();
} }
} }
@ -331,47 +355,48 @@ async fn main() -> Result<()> {
} }
if root_token.is_empty() { 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."); anyhow::bail!("Unable to find root token. Please set VAULT_TOKEN environment variable or provide vault-credentials.json file.");
} }
} else { } else {
// Initialize Vault // 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?; let init_response = init_vault(&client, &vault_addr).await?;
// Save credentials to files // 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")?; let current_dir = std::env::current_dir().context("Failed to get current directory")?;
// Save as JSON (new format) // Save as JSON (new format)
let json_path = current_dir.join("vault-credentials.json"); let json_path = current_dir.join("vault-credentials.json");
save_credentials(&init_response, json_path.to_str().unwrap())?; 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) // Save as text (for backward compatibility)
let text_path = current_dir.join("vault-credentials.txt"); let text_path = current_dir.join("vault-credentials.txt");
save_credentials(&init_response, text_path.to_str().unwrap())?; 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 // 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_json_path = "/app/data/vault-credentials.json"; let docker_json_path = "/app/data/vault-credentials.json";
save_credentials(&init_response, docker_json_path)?; 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"; let docker_text_path = "/app/data/vault-credentials.txt";
save_credentials(&init_response, docker_text_path)?; 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!("========================================="); info!("=========================================");
println!("IMPORTANT: SAVE THESE CREDENTIALS SECURELY"); info!("IMPORTANT: SAVE THESE CREDENTIALS SECURELY");
println!("========================================="); info!("=========================================");
println!("Root Token: {}", init_response.root_token); info!("Root Token: {}", init_response.root_token);
println!("Unseal Keys (first 3 of 5 needed to unseal):"); info!("Unseal Keys (first 3 of 5 needed to unseal):");
for (i, key) in init_response.keys_base64.iter().enumerate() { 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 // Unseal Vault using the first three keys
let unseal_keys = init_response.keys_base64.iter() let unseal_keys = init_response.keys_base64.iter()
@ -381,7 +406,7 @@ async fn main() -> Result<()> {
unseal_vault(&client, &vault_addr, &unseal_keys).await?; unseal_vault(&client, &vault_addr, &unseal_keys).await?;
println!("Vault initialization and unseal complete!"); info!("Vault initialization and unseal complete!");
// Set root token // Set root token
root_token = init_response.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 // Look for any existing credentials and copy them to the mounted volume
if let Ok(metadata) = std::fs::metadata("vault-credentials.json") { if let Ok(metadata) = std::fs::metadata("vault-credentials.json") {
if metadata.is_file() { 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") { if let Ok(()) = std::fs::create_dir_all("/app/data") {
match std::fs::copy("vault-credentials.json", "/app/data/vault-credentials.json") { match std::fs::copy("vault-credentials.json", "/app/data/vault-credentials.json") {
Ok(_) => println!("JSON credentials saved to Docker volume"), Ok(_) => info!("JSON credentials saved to Docker volume"),
Err(e) => println!("Failed to copy JSON credentials: {}", e), 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 let Ok(metadata) = std::fs::metadata("vault-credentials.txt") {
if metadata.is_file() { 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") { if let Ok(()) = std::fs::create_dir_all("/app/data") {
match std::fs::copy("vault-credentials.txt", "/app/data/vault-credentials.txt") { match std::fs::copy("vault-credentials.txt", "/app/data/vault-credentials.txt") {
Ok(_) => println!("Text credentials saved to Docker volume"), Ok(_) => info!("Text credentials saved to Docker volume"),
Err(e) => println!("Failed to copy text credentials: {}", e), Err(e) => warn!("Failed to copy text credentials: {}", e),
} }
} }
} }
} }
println!("Vault setup complete!"); info!("Vault setup complete!");
println!("Starting hierarchical document signing API..."); info!("Starting hierarchical document signing API...");
// Start the hierarchical signing API // Start the hierarchical signing API
start_api(&vault_addr, &root_token, api_port).await?; start_api(&vault_addr, &root_token, api_port).await?;
println!("API server shutdown. Exiting."); info!("API server shutdown. Exiting.");
Ok(()) Ok(())
} }

View file

@ -2,6 +2,7 @@ use anyhow::{Context, Result};
use reqwest::{Client, StatusCode}; use reqwest::{Client, StatusCode};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::json; use serde_json::json;
use tracing::{info, error, debug, instrument};
// Department types for organizational structure // Department types for organizational structure
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@ -35,8 +36,9 @@ impl VaultClient {
} }
// Enable required secrets engines // Enable required secrets engines
#[instrument(skip(self))]
pub async fn setup_secrets_engines(&self) -> Result<()> { 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 // Enable Transit for document signing
self.enable_secrets_engine("transit", "transit").await?; self.enable_secrets_engine("transit", "transit").await?;
@ -47,13 +49,14 @@ impl VaultClient {
// Enable userpass for authentication // Enable userpass for authentication
self.enable_auth_method("userpass").await?; self.enable_auth_method("userpass").await?;
println!("Secrets engines setup complete!"); info!("Secrets engines setup complete!");
Ok(()) Ok(())
} }
// Enable a secrets engine // Enable a secrets engine
#[instrument(skip(self))]
async fn enable_secrets_engine(&self, engine_type: &str, path: &str) -> Result<()> { 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 url = format!("{}/v1/sys/mounts/{}", self.addr, path);
let payload = json!({ let payload = json!({
@ -69,29 +72,32 @@ impl VaultClient {
match response.status() { match response.status() {
StatusCode::NO_CONTENT | StatusCode::OK => { StatusCode::NO_CONTENT | StatusCode::OK => {
println!("Successfully enabled {} engine at {}", engine_type, path); info!("Successfully enabled {} engine at {}", engine_type, path);
Ok(()) Ok(())
} }
StatusCode::BAD_REQUEST => { StatusCode::BAD_REQUEST => {
// Check if already exists // Check if already exists
let error_text = response.text().await?; let error_text = response.text().await?;
if error_text.contains("path is already in use") { if error_text.contains("path is already in use") {
println!("Secrets engine already enabled at {}", path); info!("Secrets engine already enabled at {}", path);
Ok(()) Ok(())
} else { } else {
error!("Failed to enable secrets engine: {}", error_text);
Err(anyhow::anyhow!("Failed to enable secrets engine: {}", error_text)) Err(anyhow::anyhow!("Failed to enable secrets engine: {}", error_text))
} }
} }
status => { status => {
let error_text = response.text().await?; 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)) Err(anyhow::anyhow!("Failed to enable secrets engine: {} - {}", status, error_text))
} }
} }
} }
// Enable an auth method // Enable an auth method
#[instrument(skip(self))]
async fn enable_auth_method(&self, method: &str) -> Result<()> { 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 url = format!("{}/v1/sys/auth/{}", self.addr, method);
let payload = json!({ let payload = json!({
@ -107,29 +113,32 @@ impl VaultClient {
match response.status() { match response.status() {
StatusCode::NO_CONTENT | StatusCode::OK => { StatusCode::NO_CONTENT | StatusCode::OK => {
println!("Successfully enabled {} auth method", method); info!("Successfully enabled {} auth method", method);
Ok(()) Ok(())
} }
StatusCode::BAD_REQUEST => { StatusCode::BAD_REQUEST => {
// Check if already exists // Check if already exists
let error_text = response.text().await?; let error_text = response.text().await?;
if error_text.contains("path is already in use") { if error_text.contains("path is already in use") {
println!("Auth method already enabled at {}", method); info!("Auth method already enabled at {}", method);
Ok(()) Ok(())
} else { } else {
error!("Failed to enable auth method: {}", error_text);
Err(anyhow::anyhow!("Failed to enable auth method: {}", error_text)) Err(anyhow::anyhow!("Failed to enable auth method: {}", error_text))
} }
} }
status => { status => {
let error_text = response.text().await?; 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)) Err(anyhow::anyhow!("Failed to enable auth method: {} - {}", status, error_text))
} }
} }
} }
// Create a new user in Vault and associate with department // 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<()> { 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 // Step 1: Create a policy for the user
let policy_name = format!("{}-policy", username); let policy_name = format!("{}-policy", username);
@ -151,7 +160,7 @@ impl VaultClient {
match response.status() { match response.status() {
StatusCode::NO_CONTENT | StatusCode::OK => { StatusCode::NO_CONTENT | StatusCode::OK => {
println!("Successfully created user {}", username); info!("Successfully created user {}", username);
// Step 3: Create a signing key for the user // Step 3: Create a signing key for the user
self.create_signing_key(username).await?; self.create_signing_key(username).await?;
@ -163,12 +172,14 @@ impl VaultClient {
} }
status => { status => {
let error_text = response.text().await?; let error_text = response.text().await?;
error!("Failed to create user: {} - {}", status, error_text);
Err(anyhow::anyhow!("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 // 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<()> { async fn create_signing_policy(&self, policy_name: &str, department: Department) -> Result<()> {
let dept_name = match department { let dept_name = match department {
Department::Legal => "legal", Department::Legal => "legal",
@ -212,17 +223,19 @@ impl VaultClient {
match response.status() { match response.status() {
StatusCode::NO_CONTENT | StatusCode::OK => { StatusCode::NO_CONTENT | StatusCode::OK => {
println!("Successfully created policy {}", policy_name); info!("Successfully created policy {}", policy_name);
Ok(()) Ok(())
} }
status => { status => {
let error_text = response.text().await?; let error_text = response.text().await?;
error!("Failed to create policy: {} - {}", status, error_text);
Err(anyhow::anyhow!("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 // Create a signing key for a user in the Transit engine
#[instrument(skip(self))]
async fn create_signing_key(&self, username: &str) -> Result<()> { async fn create_signing_key(&self, username: &str) -> Result<()> {
let url = format!("{}/v1/transit/keys/{}", self.addr, username); let url = format!("{}/v1/transit/keys/{}", self.addr, username);
let payload = json!({ let payload = json!({
@ -238,17 +251,19 @@ impl VaultClient {
match response.status() { match response.status() {
StatusCode::NO_CONTENT | StatusCode::OK => { StatusCode::NO_CONTENT | StatusCode::OK => {
println!("Successfully created signing key for {}", username); info!("Successfully created signing key for {}", username);
Ok(()) Ok(())
} }
status => { status => {
let error_text = response.text().await?; 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)) Err(anyhow::anyhow!("Failed to create signing key: {} - {}", status, error_text))
} }
} }
} }
// Store user metadata in the KV store // Store user metadata in the KV store
#[instrument(skip(self))]
async fn store_user_metadata(&self, username: &str, department: Department) -> Result<()> { async fn store_user_metadata(&self, username: &str, department: Department) -> Result<()> {
let dept_str = match department { let dept_str = match department {
Department::Legal => "legal", Department::Legal => "legal",
@ -272,22 +287,27 @@ impl VaultClient {
match response.status() { match response.status() {
StatusCode::NO_CONTENT | StatusCode::OK => { StatusCode::NO_CONTENT | StatusCode::OK => {
println!("Successfully stored metadata for user {}", username); info!("Successfully stored metadata for user {}", username);
Ok(()) Ok(())
} }
status => { status => {
let error_text = response.text().await?; 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)) Err(anyhow::anyhow!("Failed to store user metadata: {} - {}", status, error_text))
} }
} }
} }
// Create 10 users with departmental hierarchy - 5 in each department // Create 10 users with departmental hierarchy - 5 in each department
#[instrument(skip(self))]
pub async fn setup_hierarchical_users(&self) -> Result<()> { pub async fn setup_hierarchical_users(&self) -> Result<()> {
info!("Setting up hierarchical user structure");
// Create 5 users in Legal department // Create 5 users in Legal department
for i in 1..=5 { for i in 1..=5 {
let username = format!("legal{}", i); let username = format!("legal{}", i);
let password = format!("legal{}pass", i); let password = format!("legal{}pass", i);
debug!(username, "Creating Legal department user");
self.create_user(&username, &password, Department::Legal).await?; self.create_user(&username, &password, Department::Legal).await?;
} }
@ -295,18 +315,22 @@ impl VaultClient {
for i in 1..=5 { for i in 1..=5 {
let username = format!("finance{}", i); let username = format!("finance{}", i);
let password = format!("finance{}pass", i); let password = format!("finance{}pass", i);
debug!(username, "Creating Finance department user");
self.create_user(&username, &password, Department::Finance).await?; self.create_user(&username, &password, Department::Finance).await?;
} }
// Setup document signing requirements // Setup document signing requirements
self.setup_signing_requirements().await?; self.setup_signing_requirements().await?;
println!("Successfully created 10 users in hierarchical structure!"); info!("Successfully created 10 users in hierarchical structure!");
Ok(()) Ok(())
} }
// Configure document signing requirements // Configure document signing requirements
#[instrument(skip(self))]
async fn setup_signing_requirements(&self) -> Result<()> { 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 url = format!("{}/v1/documents/data/config/signing_requirements", self.addr);
let payload = json!({ let payload = json!({
"data": { "data": {
@ -336,18 +360,22 @@ impl VaultClient {
match response.status() { match response.status() {
StatusCode::NO_CONTENT | StatusCode::OK => { StatusCode::NO_CONTENT | StatusCode::OK => {
println!("Successfully configured signing requirements"); info!("Successfully configured signing requirements");
Ok(()) Ok(())
} }
status => { status => {
let error_text = response.text().await?; 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)) Err(anyhow::anyhow!("Failed to configure signing requirements: {} - {}", status, error_text))
} }
} }
} }
// Login a user and get their token // Login a user and get their token
#[instrument(skip(self, password))]
pub async fn login_user(&self, username: &str, password: &str) -> Result<String> { pub async fn login_user(&self, username: &str, password: &str) -> Result<String> {
info!("Attempting user login: {}", username);
let url = format!("{}/v1/auth/userpass/login/{}", self.addr, username); let url = format!("{}/v1/auth/userpass/login/{}", self.addr, username);
let payload = json!({ let payload = json!({
"password": password, "password": password,
@ -367,18 +395,22 @@ impl VaultClient {
.context("Failed to extract client token")? .context("Failed to extract client token")?
.to_string(); .to_string();
println!("User {} successfully logged in", username); info!("User {} successfully logged in", username);
Ok(token) Ok(token)
} }
status => { status => {
let error_text = response.text().await?; let error_text = response.text().await?;
error!("Failed to login: {} - {}", status, error_text);
Err(anyhow::anyhow!("Failed to login: {} - {}", status, error_text)) Err(anyhow::anyhow!("Failed to login: {} - {}", status, error_text))
} }
} }
} }
// Get user info including department // Get user info including department
#[instrument(skip(self))]
pub async fn get_user_info(&self, username: &str) -> Result<User> { pub async fn get_user_info(&self, username: &str) -> Result<User> {
debug!("Getting user info for: {}", username);
let url = format!("{}/v1/documents/data/users/{}", self.addr, username); let url = format!("{}/v1/documents/data/users/{}", self.addr, username);
let response = self.client let response = self.client
@ -397,9 +429,13 @@ impl VaultClient {
let department = match department_str { let department = match department_str {
"legal" => Department::Legal, "legal" => Department::Legal,
"finance" => Department::Finance, "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 { Ok(User {
username: username.to_string(), username: username.to_string(),
department, department,
@ -407,6 +443,7 @@ impl VaultClient {
} }
status => { status => {
let error_text = response.text().await?; 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)) Err(anyhow::anyhow!("Failed to get user info: {} - {}", status, error_text))
} }
} }