enigma_node_registry/
server.rs1use std::net::SocketAddr;
2use std::sync::Arc;
3
4use actix_web::dev::ServerHandle;
5use actix_web::web;
6use actix_web::{App, HttpResponse, HttpServer};
7use tokio::sync::oneshot;
8
9#[cfg(feature = "tls")]
10use std::fs::File;
11#[cfg(feature = "tls")]
12use std::io::BufReader;
13
14#[cfg(feature = "tls")]
15use crate::config::TlsConfig;
16use crate::config::{RegistryConfig, ServerMode};
17use crate::envelope::{EnvelopeCrypto, EnvelopeKeySet};
18use crate::error::{ErrorBody, ErrorResponse, RegistryError, RegistryResult};
19use crate::pow::PowManager as PowManagerStub;
20use crate::rate_limit::RateLimiter;
21use crate::routes::{configure, AppState};
22use crate::store::Store;
23use crate::ttl;
24
25pub struct RunningServer {
26 pub base_url: String,
27 handle: ServerHandle,
28 shutdown: oneshot::Sender<()>,
29 join: tokio::task::JoinHandle<RegistryResult<()>>,
30 #[cfg(test)]
31 pub store: Arc<Store>,
32}
33
34impl RunningServer {
35 pub async fn stop(self) -> RegistryResult<()> {
36 let _ = self.shutdown.send(());
37 self.handle.stop(true).await;
38 self.join
39 .await
40 .unwrap_or_else(|_| Err(RegistryError::Internal))
41 }
42}
43
44pub async fn start(cfg: RegistryConfig) -> RegistryResult<RunningServer> {
45 cfg.validate()?;
46 let pepper = cfg.pepper_bytes();
47 let store = build_store(&cfg, pepper)?;
48 let store_arc = Arc::new(store);
49 let keys = EnvelopeKeySet::from_config(&cfg.envelope)?;
50 let crypto = EnvelopeCrypto::new(pepper);
51 let rate_limiter = RateLimiter::new(cfg.rate_limit.clone());
52 let pow = PowManagerStub::new(cfg.pow.clone());
53 let state = AppState {
54 store: store_arc.clone(),
55 keys,
56 crypto,
57 rate_limiter,
58 pow,
59 presence_ttl: cfg.presence.ttl_seconds,
60 allow_sync: cfg.allow_sync,
61 trusted_proxies: Arc::new(cfg.trusted_proxies.clone()),
62 };
63 let bind_addr: SocketAddr = cfg
64 .address
65 .parse()
66 .map_err(|_| RegistryError::Config("invalid address".to_string()))?;
67 let presence_cfg = cfg.presence.clone();
68 let (gc_tx, gc_rx) = oneshot::channel();
69 let gc_store = store_arc.clone();
70 let gc_task = tokio::spawn(async move {
71 ttl::run_purger(gc_store, presence_cfg, gc_rx).await;
72 });
73 let (srv, base_url) = build_server(cfg, state, bind_addr).await?;
74 let handle = srv.handle();
75 let server_task = tokio::spawn(async move { srv.await.map_err(|_| RegistryError::Internal) });
76 let join = tokio::spawn(async move {
77 let res = server_task
78 .await
79 .unwrap_or_else(|_| Err(RegistryError::Internal));
80 let _ = gc_task.await;
81 res
82 });
83 Ok(RunningServer {
84 base_url,
85 handle,
86 shutdown: gc_tx,
87 join,
88 #[cfg(test)]
89 store: store_arc,
90 })
91}
92
93async fn build_server(
94 cfg: RegistryConfig,
95 state: AppState,
96 addr: SocketAddr,
97) -> RegistryResult<(actix_web::dev::Server, String)> {
98 let json_config = web::JsonConfig::default().error_handler(|err, _req| {
99 let body = ErrorResponse {
100 error: ErrorBody {
101 code: "INVALID_INPUT".to_string(),
102 message: err.to_string(),
103 details: None,
104 },
105 };
106 actix_web::error::InternalError::from_response(err, HttpResponse::BadRequest().json(body))
107 .into()
108 });
109 let cfg_for_app = cfg.clone();
110 let state_for_app = state.clone();
111 let server_factory = move || {
112 App::new()
113 .app_data(json_config.clone())
114 .configure(configure(&cfg_for_app, state_for_app.clone()))
115 };
116 match cfg.mode {
117 ServerMode::Http => {
118 if !cfg!(feature = "http") {
119 return Err(RegistryError::FeatureDisabled("http".to_string()));
120 }
121 let server = HttpServer::new(server_factory)
122 .bind(addr)
123 .map_err(|e| RegistryError::Config(format!("failed to bind http: {}", e)))?;
124 let addrs = server.addrs().to_vec();
125 Ok((server.run(), format!("http://{}", addrs[0])))
126 }
127 ServerMode::Tls => {
128 #[cfg(feature = "tls")]
129 {
130 let tls = cfg.tls.as_ref().ok_or_else(|| {
131 RegistryError::Config("tls configuration missing".to_string())
132 })?;
133 let rustls_cfg = build_rustls(tls)?;
134 let server = HttpServer::new(server_factory)
135 .bind_rustls_021(addr, rustls_cfg)
136 .map_err(|e| RegistryError::Config(format!("failed to bind tls: {}", e)))?;
137 let addrs = server.addrs().to_vec();
138 Ok((server.run(), format!("https://{}", addrs[0])))
139 }
140 #[cfg(not(feature = "tls"))]
141 {
142 Err(RegistryError::FeatureDisabled("tls".to_string()))
143 }
144 }
145 }
146}
147
148fn build_store(cfg: &RegistryConfig, pepper: [u8; 32]) -> RegistryResult<Store> {
149 if cfg.storage.kind == "sled" {
150 #[cfg(feature = "persistence")]
151 {
152 return Store::new_persistent(pepper, &cfg.storage.path, cfg.max_nodes);
153 }
154 #[cfg(not(feature = "persistence"))]
155 {
156 return Err(RegistryError::FeatureDisabled("persistence".to_string()));
157 }
158 }
159 Ok(Store::new_in_memory(pepper, cfg.max_nodes))
160}
161
162#[cfg(feature = "tls")]
163fn build_rustls(tls: &TlsConfig) -> RegistryResult<rustls::ServerConfig> {
164 use rustls::server::AllowAnyAuthenticatedClient;
165 use rustls::ServerConfig;
166 let cert_chain = load_certs(&tls.cert_pem_path)?;
167 let key = load_key(&tls.key_pem_path)?;
168 let builder = ServerConfig::builder().with_safe_defaults();
169 let config = if let Some(ca_path) = &tls.client_ca_pem_path {
170 if !cfg!(feature = "mtls") {
171 return Err(RegistryError::FeatureDisabled("mtls".to_string()));
172 }
173 let roots = load_ca(ca_path)?;
174 let verifier = AllowAnyAuthenticatedClient::new(roots);
175 builder
176 .with_client_cert_verifier(Arc::new(verifier))
177 .with_single_cert(cert_chain, key)
178 .map_err(|e| RegistryError::Config(format!("invalid certificate: {}", e)))?
179 } else {
180 builder
181 .with_no_client_auth()
182 .with_single_cert(cert_chain, key)
183 .map_err(|e| RegistryError::Config(format!("invalid certificate: {}", e)))?
184 };
185 Ok(config)
186}
187
188#[cfg(feature = "tls")]
189fn load_certs(path: &str) -> RegistryResult<Vec<rustls::Certificate>> {
190 let mut reader = BufReader::new(File::open(path)?);
191 let certs = rustls_pemfile::certs(&mut reader)
192 .map_err(|_| RegistryError::Config("failed to read certs".to_string()))?;
193 Ok(certs.into_iter().map(rustls::Certificate).collect())
194}
195
196#[cfg(feature = "tls")]
197fn load_key(path: &str) -> RegistryResult<rustls::PrivateKey> {
198 let mut reader = BufReader::new(File::open(path)?);
199 let mut keys = rustls_pemfile::pkcs8_private_keys(&mut reader)
200 .map_err(|_| RegistryError::Config("failed to read private key".to_string()))?;
201 if let Some(key) = keys.pop() {
202 return Ok(rustls::PrivateKey(key));
203 }
204 let mut reader = BufReader::new(File::open(path)?);
205 let mut keys = rustls_pemfile::rsa_private_keys(&mut reader)
206 .map_err(|_| RegistryError::Config("failed to read private key".to_string()))?;
207 keys.pop()
208 .map(rustls::PrivateKey)
209 .ok_or_else(|| RegistryError::Config("no usable private key found".to_string()))
210}
211
212#[cfg(feature = "tls")]
213fn load_ca(path: &str) -> RegistryResult<rustls::RootCertStore> {
214 let mut reader = BufReader::new(File::open(path)?);
215 let mut roots = rustls::RootCertStore::empty();
216 let certs = rustls_pemfile::certs(&mut reader)
217 .map_err(|_| RegistryError::Config("failed to read client ca".to_string()))?;
218 for cert in certs {
219 roots
220 .add(&rustls::Certificate(cert))
221 .map_err(|_| RegistryError::Config("invalid client ca".to_string()))?;
222 }
223 Ok(roots)
224}
225
226#[cfg(all(test, feature = "tls"))]
227mod tests {
228 use super::*;
229 use rand::RngCore;
230 #[cfg(feature = "tls")]
231 use rcgen;
232
233 #[cfg(feature = "tls")]
234 fn write_temp(content: &str, label: &str) -> String {
235 let mut path = std::env::temp_dir();
236 let unique = rand::thread_rng().next_u64();
237 path.push(format!("enigma-registry-{label}-{unique}.pem"));
238 std::fs::write(&path, content).expect("write temp");
239 path.to_string_lossy().to_string()
240 }
241
242 #[cfg(feature = "tls")]
243 #[actix_rt::test]
244 async fn tls_server_builder_does_not_panic() {
245 let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_string()]).unwrap();
246 let cert_pem = cert.serialize_pem().unwrap();
247 let key_pem = cert.serialize_private_key_pem();
248 let cert_path = write_temp(&cert_pem, "cert");
249 let key_path = write_temp(&key_pem, "key");
250 let tls_cfg = TlsConfig {
251 cert_pem_path: cert_path.clone(),
252 key_pem_path: key_path.clone(),
253 client_ca_pem_path: None,
254 };
255 let cfg = build_rustls(&tls_cfg);
256 std::fs::remove_file(cert_path).ok();
257 std::fs::remove_file(key_path).ok();
258 assert!(cfg.is_ok());
259 }
260
261 #[cfg(all(feature = "tls", feature = "mtls"))]
262 #[actix_rt::test]
263 async fn mtls_config_loads() {
264 let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_string()]).unwrap();
265 let cert_pem = cert.serialize_pem().unwrap();
266 let key_pem = cert.serialize_private_key_pem();
267 let ca_path = write_temp(&cert_pem, "ca");
268 let cert_path = write_temp(&cert_pem, "cert");
269 let key_path = write_temp(&key_pem, "key");
270 let tls_cfg = TlsConfig {
271 cert_pem_path: cert_path.clone(),
272 key_pem_path: key_path.clone(),
273 client_ca_pem_path: Some(ca_path.clone()),
274 };
275 let cfg = build_rustls(&tls_cfg);
276 std::fs::remove_file(cert_path).ok();
277 std::fs::remove_file(key_path).ok();
278 std::fs::remove_file(ca_path).ok();
279 assert!(cfg.is_ok());
280 }
281}