nixfleet_control_plane/polling/
bootstrap_nonces_poll.rs1use std::path::PathBuf;
5use std::sync::Arc;
6use std::sync::atomic::{AtomicBool, Ordering};
7use std::time::Duration;
8
9use anyhow::Result;
10use tokio::sync::RwLock;
11
12use crate::db::allowed_nonces::AllowedNoncesView;
13use crate::polling::poller::SignedArtifactPoller;
14use crate::polling::signed_fetch;
15
16pub const POLL_INTERVAL: Duration = Duration::from_secs(60);
17
18#[derive(Debug, Clone)]
19pub struct BootstrapNoncesSource {
20 pub artifact_url: String,
21 pub signature_url: String,
22 pub token_file: Option<PathBuf>,
23 pub trust_path: PathBuf,
24 pub freshness_window: Duration,
25}
26
27pub fn spawn(
32 cancel: tokio_util::sync::CancellationToken,
33 allowed_nonces: Arc<RwLock<AllowedNoncesView>>,
34 config: BootstrapNoncesSource,
35 bootstrap_nonces_primed: Arc<AtomicBool>,
36) -> tokio::task::JoinHandle<()> {
37 SignedArtifactPoller {
38 interval: POLL_INTERVAL,
39 label: "bootstrap_nonces",
40 }
41 .spawn(cancel, move |client| {
42 let allowed_nonces = Arc::clone(&allowed_nonces);
43 let config = config.clone();
44 let bootstrap_nonces_primed = Arc::clone(&bootstrap_nonces_primed);
45 async move {
46 let bn = poll_once(&client, &config).await?;
47 let entries = bn.bootstrap_nonces.len();
48 apply_verified_allowlist(&allowed_nonces, bn).await;
49 let was_primed = bootstrap_nonces_primed.swap(true, Ordering::AcqRel);
50 if !was_primed {
51 tracing::info!(
52 target: "bootstrap_nonces",
53 entries = entries,
54 "bootstrap nonces primed: first verified allowlist applied",
55 );
56 }
57 Ok(())
58 }
59 })
60}
61
62async fn apply_verified_allowlist(
64 allowed_nonces: &RwLock<AllowedNoncesView>,
65 bn: nixfleet_proto::BootstrapNonces,
66) {
67 let entries = bn.bootstrap_nonces.len();
68 let signed_at = bn.meta.signed_at;
69 let ci_commit = bn.meta.ci_commit.clone();
70 let view = AllowedNoncesView::from_artifact(bn);
71 let mut guard = allowed_nonces.write().await;
72 *guard = view;
73 drop(guard);
74 tracing::info!(
75 target: "bootstrap_nonces",
76 entries = entries,
77 signed_at = ?signed_at,
78 ci_commit = ?ci_commit,
79 "bootstrap-nonces poll: allowlist verified + applied",
80 );
81}
82
83async fn poll_once(
84 client: &reqwest::Client,
85 config: &BootstrapNoncesSource,
86) -> Result<nixfleet_proto::BootstrapNonces> {
87 let token = signed_fetch::read_token(config.token_file.as_deref())?;
88 let (artifact_bytes, signature_bytes) = signed_fetch::fetch_signed_pair(
89 client,
90 &config.artifact_url,
91 &config.signature_url,
92 token.as_deref(),
93 )
94 .await?;
95
96 let (trusted_keys, reject_before) =
97 signed_fetch::read_trust_roots(&config.trust_path, chrono::Utc::now())?;
98
99 nixfleet_reconciler::verify_bootstrap_nonces(
100 &artifact_bytes,
101 &signature_bytes,
102 &trusted_keys,
103 chrono::Utc::now(),
104 config.freshness_window,
105 reject_before,
106 )
107 .map(|v| v.into_inner())
108 .map_err(|e| anyhow::anyhow!("verify_bootstrap_nonces (bootstrap-nonces poll): {e:?}"))
109}