Skip to main content

vcon_server/
serve.rs

1//! `vcon-server serve`: the server, run from its configuration.
2
3use std::fmt;
4use std::future::Future;
5use std::io;
6use std::sync::Arc;
7use std::time::Duration;
8
9use tokio::net::TcpListener;
10
11use crate::api::{self, App, Limits, TlsError};
12use crate::clock::{Clock, SystemClock};
13use crate::config::Config;
14use crate::datadir::{self, LockError, Work};
15use crate::envelope::{Key, KeyError};
16use crate::fs::OsFs;
17use crate::retention::Retention;
18use crate::tenants::{KeyOrder, RotationBacklog, TenantError, Tenants};
19use time::OffsetDateTime;
20
21/// Why the server did not start, or stopped.
22#[derive(Debug)]
23pub enum ServeError {
24    /// The data directory could not be taken.
25    DataDir(LockError),
26    /// The master key file could not be read.
27    MasterKey(KeyError),
28    /// server.sqlite was made under another master key.
29    WrongMasterKey,
30    /// server.sqlite could not be opened.
31    Registry(TenantError),
32    /// Some tenant's data is not under its current key.
33    KeysOutOfOrder(KeyOrder),
34    /// The certificate or key could not be used.
35    Tls(TlsError),
36    /// The listening address could not be bound.
37    Listen(io::Error),
38    /// Serving failed.
39    Serve(io::Error),
40}
41
42impl fmt::Display for ServeError {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        match self {
45            ServeError::DataDir(e) => write!(f, "{e}"),
46            ServeError::MasterKey(e) => write!(f, "the master key cannot be read: {e:?}"),
47            ServeError::WrongMasterKey => {
48                f.write_str("the master key is not the one server.sqlite was made under")
49            }
50            ServeError::Registry(e) => write!(f, "server.sqlite cannot be opened: {e:?}"),
51            ServeError::KeysOutOfOrder(order) => {
52                let mut parts = Vec::new();
53                if order.wrapped_elsewhere > 0 {
54                    parts.push(format!(
55                        "{} tenant key(s) do not unwrap under the master key",
56                        order.wrapped_elsewhere
57                    ));
58                }
59                if order.rotations_stopped > 0 {
60                    parts.push(format!(
61                        "{} key rotation(s) stopped part way; run rekey tenant again",
62                        order.rotations_stopped
63                    ));
64                }
65                if order.data_under_other_keys > 0 {
66                    parts.push(format!(
67                        "{} tenant(s) with data under another key",
68                        order.data_under_other_keys
69                    ));
70                }
71                write!(
72                    f,
73                    "{}; vcon-server check names the tenants",
74                    parts.join("; ")
75                )
76            }
77            ServeError::Tls(e) => write!(f, "the TLS certificate or key cannot be used: {e:?}"),
78            ServeError::Listen(e) => write!(f, "the listening address cannot be bound: {e}"),
79            ServeError::Serve(e) => write!(f, "serving failed: {e}"),
80        }
81    }
82}
83
84/// Runs the server until `shutdown` completes: locks the data directory,
85/// opens the registry under the master key, and serves the API over TLS.
86pub async fn run(config: &Config, shutdown: impl Future<Output = ()>) -> Result<(), ServeError> {
87    let _lock =
88        datadir::lock(&config.data_dir, Work::Serve.sharing()).map_err(ServeError::DataDir)?;
89    let master = Key::load_master(&config.master_key).map_err(ServeError::MasterKey)?;
90    let tenants = Tenants::open(
91        &config.data_dir,
92        master,
93        Arc::new(OsFs),
94        Arc::new(SystemClock),
95    )
96    .map_err(|e| match e {
97        TenantError::WrongKey => ServeError::WrongMasterKey,
98        other => ServeError::Registry(other),
99    })?;
100    let order = tenants.key_order().map_err(ServeError::Registry)?;
101    if !order.is_sound() {
102        return Err(ServeError::KeysOutOfOrder(order));
103    }
104    let tls = api::tls_config(&config.tls_cert, &config.tls_key).map_err(ServeError::Tls)?;
105    let listener = TcpListener::bind(config.listen)
106        .await
107        .map_err(ServeError::Listen)?;
108    let tenants = Arc::new(tenants);
109    // The purge sweep: at start, then purge_interval after each sweep ends,
110    // until the server stops.
111    let (stop, stopped) = tokio::sync::oneshot::channel::<()>();
112    let (swept, default) = (tenants.clone(), config.retention_default);
113    let sweeper = tokio::spawn(every(
114        config.purge_interval,
115        async {
116            let _ = stopped.await;
117        },
118        move || {
119            let tenants = swept.clone();
120            async move {
121                let _ = tokio::task::spawn_blocking(move || sweep(&tenants, default)).await;
122            }
123        },
124    ));
125    // The rotation warning: at start, then each day.
126    let (stop_warning, warning_stopped) = tokio::sync::oneshot::channel::<()>();
127    let (watched, rotation_warn) = (tenants.clone(), config.rotation_warn);
128    let warner = tokio::spawn(every(
129        ROTATION_CHECK,
130        async {
131            let _ = warning_stopped.await;
132        },
133        move || {
134            let tenants = watched.clone();
135            async move {
136                let _ = tokio::task::spawn_blocking(move || {
137                    warn_if_rotation_overdue(&tenants, rotation_warn);
138                })
139                .await;
140            }
141        },
142    ));
143    let app = app_from(config, tenants);
144    tracing::info!(listen = %config.listen, "serving");
145    let served = api::serve(listener, tls, api::router(Arc::new(app)), shutdown).await;
146    let _ = stop.send(());
147    let _ = stop_warning.send(());
148    let _ = sweeper.await;
149    let _ = warner.await;
150    served.map_err(ServeError::Serve)?;
151    tracing::info!("stopped");
152    Ok(())
153}
154
155/// What one purge sweep did.
156#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
157pub struct SweepReport {
158    /// Versions purged, over every tenant.
159    pub purged: u64,
160    /// Tenants that could not be swept; they wait for the next sweep.
161    pub failed: u64,
162}
163
164/// One purge sweep: each tenant purges what is due under its own retention
165/// period, else `default`. A tenant that cannot be swept, such as one whose
166/// period does not read back or that is being removed, is counted and left
167/// for the next sweep. The sweep is logged with counts only, never a tenant.
168pub fn sweep(tenants: &Tenants, default: Retention) -> SweepReport {
169    let mut report = SweepReport::default();
170    match tenants.ids() {
171        Ok(ids) => {
172            for id in ids {
173                let purged = tenants.retention(id).and_then(|own| {
174                    let store = tenants.store(id)?;
175                    Ok(store.purge_due(own.unwrap_or(default)))
176                });
177                match purged {
178                    Ok(Ok(done)) => report.purged += done.purged,
179                    _ => report.failed += 1,
180                }
181            }
182        }
183        Err(_) => report.failed += 1,
184    }
185    tracing::info!(
186        purged = report.purged,
187        failed = report.failed,
188        "purge sweep"
189    );
190    report
191}
192
193/// Runs `job` at once, then again `interval` after each run ends, until
194/// `shutdown` completes; a run still going when it does is not awaited.
195pub async fn every<F, Fut>(interval: Duration, shutdown: impl Future<Output = ()>, mut job: F)
196where
197    F: FnMut() -> Fut,
198    Fut: Future<Output = ()>,
199{
200    tokio::pin!(shutdown);
201    loop {
202        tokio::select! {
203            biased;
204            () = &mut shutdown => return,
205            () = job() => {}
206        }
207        tokio::select! {
208            biased;
209            () = &mut shutdown => return,
210            () = tokio::time::sleep(interval) => {}
211        }
212    }
213}
214
215/// What the API serves from, as the configuration sets it.
216fn app_from(config: &Config, tenants: Arc<Tenants>) -> App {
217    App {
218        tenants,
219        limits: Limits {
220            body: config.body_limit,
221            inflated: config.inflated_limit,
222            body_timeout: config.read_timeout,
223        },
224        supported: config.supported_critical.clone(),
225        retention_default: config.retention_default,
226    }
227}
228
229/// Whether key rotation is overdue: the oldest purge or removal waiting
230/// for it is older than `rotation_warn`.
231pub fn rotation_overdue(
232    backlog: &RotationBacklog,
233    rotation_warn: Duration,
234    now: OffsetDateTime,
235) -> bool {
236    [backlog.oldest_purge, backlog.oldest_removal]
237        .into_iter()
238        .flatten()
239        .any(|at| now - at > rotation_warn)
240}
241
242/// How often `serve` looks at the rotation backlog.
243const ROTATION_CHECK: Duration = Duration::from_secs(86_400);
244
245/// Logs a warning, with counts only, when key rotation is overdue.
246fn warn_if_rotation_overdue(tenants: &Tenants, rotation_warn: Duration) {
247    match tenants.rotation_backlog() {
248        Ok(backlog) if rotation_overdue(&backlog, rotation_warn, SystemClock.now()) => {
249            tracing::warn!(
250                tenants_with_purges = backlog.tenants_with_purges,
251                purges = backlog.purges,
252                removals = backlog.removals,
253                "key rotation overdue: purged vCons and removed tenants stay readable on old copies until it"
254            );
255        }
256        Ok(_) => {}
257        Err(_) => tracing::warn!("the key rotation backlog could not be read"),
258    }
259}
260
261#[cfg(test)]
262mod tests {
263    use std::time::Duration;
264
265    use super::*;
266
267    mod startup {
268        use time::macros::datetime;
269
270        use super::*;
271
272        const NOW: OffsetDateTime = datetime!(2026-10-25 12:00 UTC);
273        const DAY: Duration = Duration::from_secs(86_400);
274
275        #[test]
276        fn rotation_is_overdue_once_the_oldest_waiting_work_passes_rotation_warn() {
277            let backlog =
278                |purge: Option<OffsetDateTime>, removal: Option<OffsetDateTime>| RotationBacklog {
279                    tenants_with_purges: u64::from(purge.is_some()),
280                    purges: u64::from(purge.is_some()),
281                    oldest_purge: purge,
282                    removals: u64::from(removal.is_some()),
283                    oldest_removal: removal,
284                };
285            let day_ago = Some(datetime!(2026-10-24 12:00 UTC));
286            let older = Some(datetime!(2026-10-24 11:59:59 UTC));
287            for (b, overdue) in [
288                (backlog(None, None), false),
289                (backlog(day_ago, None), false),
290                (backlog(older, None), true),
291                (backlog(None, older), true),
292                (backlog(day_ago, older), true),
293            ] {
294                assert_eq!(rotation_overdue(&b, DAY, NOW), overdue, "{b:?}");
295            }
296        }
297
298        #[test]
299        fn a_refusal_for_keys_out_of_order_counts_and_points_at_check() {
300            let order = KeyOrder {
301                wrapped_elsewhere: 1,
302                rotations_stopped: 2,
303                data_under_other_keys: 3,
304            };
305            let text = ServeError::KeysOutOfOrder(order).to_string();
306            for words in [
307                "1 tenant key",
308                "2 key rotation",
309                "3 tenant",
310                "vcon-server check",
311            ] {
312                assert!(text.contains(words), "{words}: {text}");
313            }
314        }
315    }
316    use crate::retention::Retention;
317
318    #[test]
319    fn the_api_takes_its_limits_and_extensions_from_the_configuration() {
320        let dir = tempfile::tempdir().expect("temp dir");
321        let tenants = Arc::new(
322            Tenants::open(
323                dir.path(),
324                Key::generate().expect("key"),
325                Arc::new(OsFs),
326                Arc::new(SystemClock),
327            )
328            .expect("opens"),
329        );
330        let config = Config {
331            listen: "127.0.0.1:8443".parse().expect("address"),
332            tls_cert: "cert.pem".into(),
333            tls_key: "key.pem".into(),
334            data_dir: dir.path().to_owned(),
335            master_key: "master.key".into(),
336            retention_default: Retention::Months(18),
337            purge_interval: Duration::from_secs(3600),
338            rotation_warn: Duration::from_secs(86400),
339            body_limit: 12_345,
340            inflated_limit: 67_890,
341            read_timeout: Duration::from_secs(17),
342            supported_critical: ["known".to_owned()].into(),
343        };
344        let app = app_from(&config, tenants.clone());
345        assert!(Arc::ptr_eq(&app.tenants, &tenants));
346        assert_eq!(app.limits.body, 12_345);
347        assert_eq!(app.limits.inflated, 67_890);
348        assert_eq!(app.limits.body_timeout, Duration::from_secs(17));
349        assert_eq!(app.supported, config.supported_critical);
350        assert_eq!(app.retention_default, config.retention_default);
351    }
352
353    mod sweeps {
354        use std::sync::atomic::{AtomicUsize, Ordering};
355        use std::time::Duration;
356
357        use time::macros::datetime;
358        use uuid::Uuid;
359
360        use super::super::*;
361        use crate::audit::Actor;
362        use crate::ids::{KeyId, TenantId};
363        use crate::retention::Retention;
364        use crate::store::Caller;
365        use crate::store::testing::vcon;
366        use crate::tenants::fixture;
367
368        const CALLER: Caller = Caller { key: KeyId([7; 8]) };
369
370        /// A vCon stored and deleted in `id` while seven years apply, so
371        /// it waits hidden for a sweep.
372        fn deleted(tenants: &Tenants, id: TenantId, uuid: &str) -> Uuid {
373            let store = tenants.store(id).expect("its store");
374            let (found, body) = vcon(uuid, "kept");
375            store.create(&CALLER, &found, &body).expect("stored");
376            store
377                .delete(&CALLER, found.uuid, Retention::Years(7))
378                .expect("deleted");
379            found.uuid
380        }
381
382        /// How many versions `id` still keeps.
383        fn kept(tenants: &Tenants, id: TenantId) -> u64 {
384            tenants
385                .store(id)
386                .expect("its store")
387                .counts()
388                .expect("counts")
389                .versions
390        }
391
392        #[test]
393        fn a_sweep_purges_what_is_due_in_each_tenant_under_its_own_period() {
394            let f = fixture();
395            let own = f
396                .tenants
397                .create(Actor::Cli, "own", Some(Retention::Days(1)))
398                .expect("made");
399            let default = f.tenants.create(Actor::Cli, "default", None).expect("made");
400            let long = f
401                .tenants
402                .create(Actor::Cli, "long", Some(Retention::Years(7)))
403                .expect("made");
404            deleted(&f.tenants, own, "0199e5c6-0d7a-7c11-8000-000000000001");
405            deleted(&f.tenants, default, "0199e5c6-0d7a-7c11-8000-000000000002");
406            deleted(&f.tenants, long, "0199e5c6-0d7a-7c11-8000-000000000003");
407            f.clock.set(datetime!(2026-10-25 13:00 UTC));
408
409            let report = sweep(&f.tenants, Retention::Days(1));
410
411            assert_eq!(
412                report,
413                SweepReport {
414                    purged: 2,
415                    failed: 0
416                }
417            );
418            assert_eq!(kept(&f.tenants, own), 0);
419            assert_eq!(kept(&f.tenants, default), 0);
420            assert_eq!(kept(&f.tenants, long), 1, "seven years have not passed");
421        }
422
423        #[test]
424        fn a_sweep_goes_on_past_a_tenant_it_cannot_sweep() {
425            let f = fixture();
426            let damaged = f.tenants.create(Actor::Cli, "damaged", None).expect("made");
427            let fine = f.tenants.create(Actor::Cli, "fine", None).expect("made");
428            deleted(&f.tenants, damaged, "0199e5c6-0d7a-7c11-8000-000000000001");
429            deleted(&f.tenants, fine, "0199e5c6-0d7a-7c11-8000-000000000002");
430            f.tenants
431                .conn_for_test()
432                .execute(
433                    "UPDATE tenant SET retention = '7w' WHERE id = ?1",
434                    [damaged.0],
435                )
436                .expect("damaged");
437            f.clock.set(datetime!(2026-10-25 13:00 UTC));
438
439            let report = sweep(&f.tenants, Retention::Days(1));
440
441            assert_eq!(
442                report,
443                SweepReport {
444                    purged: 1,
445                    failed: 1
446                }
447            );
448            assert_eq!(kept(&f.tenants, fine), 0);
449            assert_eq!(kept(&f.tenants, damaged), 1, "left for a later sweep");
450        }
451
452        #[tokio::test(start_paused = true)]
453        async fn every_runs_at_start_then_after_each_interval_until_shutdown() {
454            let runs = Arc::new(AtomicUsize::new(0));
455            let (stop, stopped) = tokio::sync::oneshot::channel::<()>();
456            let counted = runs.clone();
457            let task = tokio::spawn(every(
458                Duration::from_secs(60),
459                async move {
460                    let _ = stopped.await;
461                },
462                move || {
463                    counted.fetch_add(1, Ordering::SeqCst);
464                    async {}
465                },
466            ));
467            let settle = || async {
468                for _ in 0..10 {
469                    tokio::task::yield_now().await;
470                }
471            };
472
473            settle().await;
474            assert_eq!(runs.load(Ordering::SeqCst), 1, "at start");
475            tokio::time::advance(Duration::from_secs(59)).await;
476            settle().await;
477            assert_eq!(runs.load(Ordering::SeqCst), 1, "not before the interval");
478            tokio::time::advance(Duration::from_secs(1)).await;
479            settle().await;
480            assert_eq!(runs.load(Ordering::SeqCst), 2, "after the interval");
481
482            stop.send(()).expect("still running");
483            task.await.expect("ends on shutdown");
484            tokio::time::advance(Duration::from_secs(600)).await;
485            assert_eq!(runs.load(Ordering::SeqCst), 2, "none after shutdown");
486        }
487    }
488}