Files
foxhunt/crates/common/src/tls.rs
jgrusewski 9c3d741a08 refactor: restructure repo — crates/, bin/, testing/ layout
Move 17 library crates into crates/, CLI binary into bin/fxt,
consolidate 10 test crates into testing/, split config crate
from deployment config files.

Root directory reduced from 38+ to ~17 directories.
All Cargo.toml paths and build.rs proto refs updated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 11:56:00 +01:00

174 lines
5.7 KiB
Rust

//! Shared TLS types for Foxhunt services.
//!
//! This module contains type definitions shared across all service TLS configurations:
//! - [`TlsProtocolVersion`] - TLS 1.2 / 1.3 selection
//! - [`UserRole`] - RBAC roles extracted from client certificates
//! - [`ClientIdentity`] - Client identity extracted from mTLS certificates
//!
//! Each service retains its own `TlsConfig` struct and validation logic because
//! the validation approaches differ (async vs sync, delegated vs monolithic).
/// TLS protocol version options.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TlsProtocolVersion {
/// TLS version 1.2
Tls12,
/// TLS version 1.3
Tls13,
}
/// User roles based on certificate attributes (RBAC).
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum UserRole {
/// Administrator with full access
Admin,
/// Trader with trading permissions
Trader,
/// Analyst with read/analysis permissions
Analyst,
/// Risk manager with risk oversight
RiskManager,
/// Compliance officer with audit access
ComplianceOfficer,
/// Read-only access
ReadOnly,
}
impl UserRole {
/// Get permissions for this role.
pub fn get_permissions(&self) -> Vec<&'static str> {
match self {
UserRole::Admin => vec![
"trading.submit_order",
"trading.cancel_order",
"trading.modify_order",
"risk.view_positions",
"risk.modify_limits",
"analytics.view_data",
"analytics.run_backtest",
"compliance.view_reports",
"system.configure",
],
UserRole::Trader => vec![
"trading.submit_order",
"trading.cancel_order",
"trading.modify_order",
"risk.view_positions",
"analytics.view_data",
],
UserRole::Analyst => vec![
"analytics.view_data",
"analytics.run_backtest",
"risk.view_positions",
],
UserRole::RiskManager => vec![
"risk.view_positions",
"risk.modify_limits",
"analytics.view_data",
"compliance.view_reports",
],
UserRole::ComplianceOfficer => vec![
"compliance.view_reports",
"analytics.view_data",
"risk.view_positions",
],
UserRole::ReadOnly => vec!["analytics.view_data"],
}
}
}
/// Client identity extracted from mTLS certificate.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ClientIdentity {
/// Common Name (CN) from certificate
pub common_name: String,
/// Organizational Unit (OU) from certificate
pub organizational_unit: String,
/// Certificate serial number
pub serial_number: String,
/// Certificate issuer
pub issuer: String,
}
impl ClientIdentity {
/// Check if client is authorized for trading operations.
pub fn is_authorized_for_trading(&self) -> bool {
matches!(self.organizational_unit.as_str(), "trading" | "admin")
}
/// Check if client is authorized for read-only operations.
pub fn is_authorized_for_readonly(&self) -> bool {
matches!(
self.organizational_unit.as_str(),
"trading" | "admin" | "analytics" | "risk" | "compliance"
)
}
/// Get user role based on certificate.
pub fn get_role(&self) -> UserRole {
match self.organizational_unit.as_str() {
"admin" => UserRole::Admin,
"trading" => UserRole::Trader,
"analytics" => UserRole::Analyst,
"risk" => UserRole::RiskManager,
"compliance" => UserRole::ComplianceOfficer,
_ => UserRole::ReadOnly,
}
}
}
#[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"));
}
#[test]
fn test_tls_protocol_version_equality() {
let tls13 = TlsProtocolVersion::Tls13;
let tls12 = TlsProtocolVersion::Tls12;
assert_ne!(tls13, tls12);
assert_eq!(tls13, TlsProtocolVersion::Tls13);
}
}