enigma_node_registry/
server.rs

1use 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}