Skip to main content

aegis_server/
config.rs

1//! Aegis Server Configuration
2//!
3//! Server configuration management for binding, TLS, and operational settings.
4//!
5//! @version 0.1.0
6//! @author AutomataNexus Development Team
7
8use serde::{Deserialize, Serialize};
9use std::net::SocketAddr;
10
11// =============================================================================
12// Server Configuration
13// =============================================================================
14
15/// Server configuration.
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct ServerConfig {
18    pub host: String,
19    pub port: u16,
20    pub max_connections: usize,
21    pub request_timeout_secs: u64,
22    pub body_limit_bytes: usize,
23    pub enable_cors: bool,
24    /// Allowed CORS origins (empty = same-origin only, "*" = any)
25    pub cors_allowed_origins: Vec<String>,
26    pub tls: Option<TlsConfig>,
27    /// TLS configuration for cluster (inter-node) communication
28    pub cluster_tls: Option<ClusterTlsConfig>,
29    pub data_dir: Option<String>,
30    /// Unique node ID
31    pub node_id: String,
32    /// Human-readable node name (e.g., "AxonML", "NexusScribe")
33    pub node_name: Option<String>,
34    /// Cluster name
35    pub cluster_name: String,
36    /// Peer addresses for cluster membership
37    pub peers: Vec<String>,
38    /// Rate limit: max requests per minute per IP
39    pub rate_limit_per_minute: u32,
40    /// Rate limit: max login attempts per minute per IP
41    pub login_rate_limit_per_minute: u32,
42    /// Whether API endpoints require authentication (default: true when admin user configured)
43    pub auth_required: bool,
44}
45
46impl Default for ServerConfig {
47    fn default() -> Self {
48        Self {
49            host: "127.0.0.1".to_string(),
50            port: 3000,
51            max_connections: 10000,
52            request_timeout_secs: 30,
53            body_limit_bytes: 10 * 1024 * 1024, // 10MB
54            enable_cors: true,
55            cors_allowed_origins: Vec::new(), // Empty = same-origin only (secure default)
56            tls: None,
57            cluster_tls: None,
58            data_dir: None,
59            node_id: generate_node_id(),
60            node_name: None,
61            cluster_name: "aegis-cluster".to_string(),
62            peers: Vec::new(),
63            rate_limit_per_minute: 100,
64            login_rate_limit_per_minute: 30,
65            auth_required: true,
66        }
67    }
68}
69
70/// Generate a unique node ID based on timestamp and random suffix
71fn generate_node_id() -> String {
72    use std::time::{SystemTime, UNIX_EPOCH};
73    let timestamp = SystemTime::now()
74        .duration_since(UNIX_EPOCH)
75        .unwrap_or_default()
76        .as_millis();
77    format!("node-{:x}", timestamp as u64)
78}
79
80impl ServerConfig {
81    /// Create a new server config with the specified host and port.
82    pub fn new(host: &str, port: u16) -> Self {
83        Self {
84            host: host.to_string(),
85            port,
86            ..Default::default()
87        }
88    }
89
90    /// Get the socket address for binding.
91    pub fn socket_addr(&self) -> SocketAddr {
92        format!("{}:{}", self.host, self.port)
93            .parse()
94            .unwrap_or_else(|_| SocketAddr::from(([127, 0, 0, 1], self.port)))
95    }
96
97    /// Enable TLS with the specified certificate and key paths.
98    pub fn with_tls(mut self, cert_path: &str, key_path: &str) -> Self {
99        self.tls = Some(TlsConfig {
100            cert_path: cert_path.to_string(),
101            key_path: key_path.to_string(),
102        });
103        self
104    }
105
106    /// Set the maximum number of concurrent connections.
107    pub fn with_max_connections(mut self, max: usize) -> Self {
108        self.max_connections = max;
109        self
110    }
111
112    /// Set the request timeout in seconds.
113    pub fn with_timeout(mut self, secs: u64) -> Self {
114        self.request_timeout_secs = secs;
115        self
116    }
117
118    /// Set the data directory for persistence.
119    pub fn with_data_dir(mut self, data_dir: Option<String>) -> Self {
120        self.data_dir = data_dir;
121        self
122    }
123
124    /// Set the node ID.
125    pub fn with_node_id(mut self, node_id: Option<String>) -> Self {
126        if let Some(id) = node_id {
127            self.node_id = id;
128        }
129        self
130    }
131
132    /// Set the node name.
133    pub fn with_node_name(mut self, node_name: Option<String>) -> Self {
134        self.node_name = node_name;
135        self
136    }
137
138    /// Set the cluster name.
139    pub fn with_cluster_name(mut self, cluster_name: String) -> Self {
140        self.cluster_name = cluster_name;
141        self
142    }
143
144    /// Set the peer addresses.
145    pub fn with_peers(mut self, peers: Vec<String>) -> Self {
146        self.peers = peers;
147        self
148    }
149
150    /// Get the full address of this node.
151    pub fn address(&self) -> String {
152        format!("{}:{}", self.host, self.port)
153    }
154
155    /// Set cluster TLS configuration.
156    pub fn with_cluster_tls(mut self, cluster_tls: Option<ClusterTlsConfig>) -> Self {
157        self.cluster_tls = cluster_tls;
158        self
159    }
160
161    /// Check if cluster TLS is enabled.
162    pub fn cluster_tls_enabled(&self) -> bool {
163        self.cluster_tls.as_ref().is_some_and(|c| c.enabled)
164    }
165}
166
167// =============================================================================
168// TLS Configuration
169// =============================================================================
170
171/// TLS configuration for HTTPS.
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct TlsConfig {
174    pub cert_path: String,
175    pub key_path: String,
176}
177
178/// TLS configuration for cluster (inter-node) communication.
179#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct ClusterTlsConfig {
181    /// Whether cluster TLS is enabled
182    pub enabled: bool,
183    /// Path to CA certificate for verifying peer certificates (PEM format).
184    /// If not provided, system root certificates are used.
185    pub ca_cert_path: Option<String>,
186    /// Path to client certificate for mutual TLS (PEM format).
187    /// Optional - only needed for mTLS.
188    pub client_cert_path: Option<String>,
189    /// Path to client private key for mutual TLS (PEM format).
190    /// Optional - only needed for mTLS.
191    pub client_key_path: Option<String>,
192    /// Whether to skip certificate verification (INSECURE - only for testing).
193    pub danger_accept_invalid_certs: bool,
194}
195
196// =============================================================================
197// Tests
198// =============================================================================
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203
204    #[test]
205    fn test_default_config() {
206        let config = ServerConfig::default();
207        assert_eq!(config.host, "127.0.0.1");
208        assert_eq!(config.port, 3000);
209        assert!(config.tls.is_none());
210    }
211
212    #[test]
213    fn test_socket_addr() {
214        let config = ServerConfig::new("0.0.0.0", 8080);
215        let addr = config.socket_addr();
216        assert_eq!(addr.port(), 8080);
217    }
218
219    #[test]
220    fn test_cluster_tls_config() {
221        let config = ServerConfig::default();
222        assert!(!config.cluster_tls_enabled());
223
224        let config_with_tls = config.with_cluster_tls(Some(ClusterTlsConfig {
225            enabled: true,
226            ca_cert_path: Some("/path/to/ca.pem".to_string()),
227            client_cert_path: Some("/path/to/cert.pem".to_string()),
228            client_key_path: Some("/path/to/key.pem".to_string()),
229            danger_accept_invalid_certs: false,
230        }));
231        assert!(config_with_tls.cluster_tls_enabled());
232
233        // Disabled TLS config should return false
234        let config_disabled = ServerConfig::default().with_cluster_tls(Some(ClusterTlsConfig {
235            enabled: false,
236            ca_cert_path: None,
237            client_cert_path: None,
238            client_key_path: None,
239            danger_accept_invalid_certs: false,
240        }));
241        assert!(!config_disabled.cluster_tls_enabled());
242    }
243}