//! TLS configuration for Backtesting Service with mutual TLS //! //! This module provides enterprise-grade TLS configuration for the backtesting service: //! - Mutual TLS (mTLS) for all gRPC connections //! - Static certificate management via config crate //! - Client certificate validation and authentication //! - Performance optimized for HFT requirements use anyhow::{Context, Result}; // Re-export shared TLS types from common for backward compatibility #[allow(unused_imports)] pub use common::tls::{ClientIdentity, TlsProtocolVersion, UserRole}; // TLS imports - TLS feature should be enabled in Cargo.toml use tonic::transport::{Certificate, Identity, ServerTlsConfig}; use tracing::info; /// TLS configuration for the trading service #[derive(Debug, Clone)] pub struct BacktestingServiceTlsConfig { /// Server certificate and private key pub server_identity: Identity, /// CA certificate for client verification pub ca_certificate: Certificate, /// Require client certificates pub require_client_cert: bool, } impl BacktestingServiceTlsConfig { /// Create TLS configuration from certificate files pub async fn from_files( cert_path: &str, key_path: &str, ca_cert_path: &str, require_client_cert: bool, ) -> Result { info!("Loading TLS certificates from filesystem"); // Read server certificate and key let cert_pem = tokio::fs::read_to_string(cert_path) .await .with_context(|| format!("Failed to read certificate file: {}", cert_path))?; let key_pem = tokio::fs::read_to_string(key_path) .await .with_context(|| format!("Failed to read private key file: {}", key_path))?; // Combine certificate and key for server identity let server_identity = Identity::from_pem(cert_pem, key_pem); // Read CA certificate for client verification let ca_pem = tokio::fs::read_to_string(ca_cert_path) .await .with_context(|| format!("Failed to read CA certificate file: {}", ca_cert_path))?; let ca_certificate = Certificate::from_pem(ca_pem); info!( "TLS certificates loaded successfully - mTLS: {}", require_client_cert ); Ok(Self { server_identity, ca_certificate, require_client_cert, }) } /// Convert to tonic ServerTlsConfig pub fn to_server_tls_config(&self) -> ServerTlsConfig { let mut tls_config = ServerTlsConfig::new().identity(self.server_identity.clone()); if self.require_client_cert { tls_config = tls_config.client_ca_root(self.ca_certificate.clone()); } tls_config } } #[cfg(test)] mod tests { use super::*; #[test] fn test_client_identity_authorization() { let trading_identity = ClientIdentity { common_name: "trader1.trading.foxhunt.internal".to_string(), organizational_unit: "trading".to_string(), serial_number: "12345".to_string(), issuer: "Foxhunt Trading CA".to_string(), }; assert!(trading_identity.is_authorized_for_trading()); assert!(trading_identity.is_authorized_for_readonly()); assert_eq!(trading_identity.get_role(), UserRole::Trader); let readonly_identity = ClientIdentity { common_name: "analyst1.analytics.foxhunt.internal".to_string(), organizational_unit: "analytics".to_string(), serial_number: "12346".to_string(), issuer: "Foxhunt Trading CA".to_string(), }; assert!(!readonly_identity.is_authorized_for_trading()); assert!(readonly_identity.is_authorized_for_readonly()); assert_eq!(readonly_identity.get_role(), UserRole::Analyst); } #[test] fn test_user_role_permissions() { let trader = UserRole::Trader; let permissions = trader.get_permissions(); assert!(permissions.contains(&"trading.submit_order")); assert!(permissions.contains(&"trading.cancel_order")); assert!(!permissions.contains(&"system.configure")); let readonly = UserRole::ReadOnly; let readonly_permissions = readonly.get_permissions(); assert!(!readonly_permissions.contains(&"trading.submit_order")); assert!(readonly_permissions.contains(&"analytics.view_data")); } }