Skip to main content

acme_proxy_server/
assembly.rs

1//! What one configuration generation hands its profiles ([`GenerationParts`]),
2//! and what outlives it ([`Assembly`]). What it dials through is
3//! [`acme_proxy_net::egress::Egress`].
4
5use std::sync::Arc;
6
7use acme_proxy_core::config;
8use acme_proxy_core::config::Config;
9use acme_proxy_jobs::metrics;
10use acme_proxy_jobs::notify;
11use acme_proxy_net::egress::Egress;
12use acme_proxy_signer as signer;
13use acme_proxy_store::db::Database;
14
15/// The three things one configuration generation contributes to its profiles,
16/// built before any of them is published.
17///
18/// A struct because [`profile::build_all_with`](super::profile::build_all_with)
19/// would otherwise take three same-shaped values positionally, and because the
20/// three are built together and must be published together —
21/// `server::generation::publish_reload` swaps the notifier map and the signer
22/// set in the same uninterruptible run as the routers built from them.
23pub struct GenerationParts {
24    pub egress: Arc<Egress>,
25    pub dispatchers: notify::DispatcherMap,
26    /// The backends, which the job handlers sign and revoke through.
27    pub signers: signer::SignerSet,
28    /// Their read sides, which the profiles serve from.
29    pub infos: signer::SignerSet<dyn signer::SignerInfo>,
30}
31
32/// What survives a configuration reload.
33///
34/// Every generation rebuilds its profiles, its routers, its job registry, its
35/// egress clients and any signer backend whose configuration moved. The things
36/// here are built once for the life of the process and handed to each generation
37/// instead — and after three rounds of moving things *off* this list, everything
38/// left is here because rebuilding it would lose something, never because
39/// rebuilding it would merely cost something:
40///
41/// - `database` and `jobs` are the pool and its enqueue side. `database.url` is
42///   the one key [`crate::reload`] still refuses, and this is why.
43/// - `metrics` is a **correctness** requirement. A registry rebuilt per
44///   generation would reset every counter on `SIGHUP`, and a counter going
45///   backwards is precisely how Prometheus recognises a process restart — so
46///   `rate()` would report the whole pre-reload total as a spike on every
47///   configuration change.
48/// - `signers` is the *previous* generation's backend set, kept so the next
49///   reload can reuse a backend whose configuration did not move (see
50///   [`signer::build_backends`]); `infos` the same for their read sides. Behind
51///   a `Mutex` because each is written once per generation; nothing reads
52///   either to serve a request, since a `Profile` holds its own read side.
53/// - `roles` decides whether there are backends at all: only a process running
54///   the `worker` role builds one, so the others never read a CA key, log in to
55///   a token or contact a relay's upstream — not at startup, not on reload.
56/// - `notifiers` is a handle rather than a map, so `[notify]` can reload
57///   underneath the backends that captured it.
58///
59/// `resolver` and `proxies` used to be here, justified by the signers caching
60/// them at construction. They moved to [`Egress`] when that stopped being a
61/// reason to freeze `[dns]`/`[proxy]` and became a reason to rebuild a signer.
62pub struct Assembly {
63    pub database: Arc<Database>,
64    pub jobs: acme_proxy_jobs::jobs::JobQueue,
65    pub metrics: Arc<metrics::Metrics>,
66    pub notifiers: notify::Notifiers,
67    notifiers_tx: notify::NotifiersSender,
68    signers: std::sync::Mutex<signer::SignerSet>,
69    /// The previous generation's read sides, kept for `signers`' reason.
70    infos: std::sync::Mutex<signer::SignerSet<dyn signer::SignerInfo>>,
71    roles: super::RoleSet,
72}
73
74impl Assembly {
75    /// Builds everything that outlives a generation, plus the first generation's
76    /// own parts.
77    ///
78    /// Those come back rather than being kept here because they are *not*
79    /// long-lived: the caller hands them to
80    /// [`profile::build_all_with`](super::profile::build_all_with) and then
81    /// forgets them, and every later generation builds its own through
82    /// [`build_parts`](Self::build_parts).
83    pub fn new(
84        roles: super::RoleSet,
85        resolved: &[config::ProfileConfig],
86        database: Arc<Database>,
87        jobs: acme_proxy_jobs::jobs::JobQueue,
88        config: &Config,
89    ) -> anyhow::Result<(Self, GenerationParts)> {
90        // Built before the signers, because the `relay` backend settles an
91        // issuance from a background task that has no request and no `Auditor`,
92        // so it counts that issuance through a handle it was given at
93        // construction.
94        let metrics = Arc::new(metrics::Metrics::new(database.clone()).with_roles(&roles.labels()));
95        // Opened over an empty map and republished immediately below, so the
96        // handle the signers capture is the one every later generation writes
97        // into.
98        let (notifiers_tx, notifiers) = notify::notifiers_channel(notify::DispatcherMap::new());
99
100        let assembly = Self {
101            database,
102            jobs,
103            metrics,
104            notifiers,
105            notifiers_tx,
106            signers: std::sync::Mutex::new(signer::SignerSet::default()),
107            infos: std::sync::Mutex::new(signer::SignerSet::default()),
108            roles,
109        };
110        let parts = assembly.build_parts(resolved, config)?;
111        // The first generation's map has to reach the handle before anything
112        // dispatches through it; every later one goes through `publish` in the
113        // reload's own synchronous run.
114        assembly.publish_notifiers(parts.dispatchers.clone());
115        assembly.publish_signers(parts.signers.clone(), parts.infos.clone());
116        Ok((assembly, parts))
117    }
118
119    /// Builds one generation's egress, dispatchers and signer backends, without
120    /// publishing any of them.
121    ///
122    /// Separate from [`publish_notifiers`](Self::publish_notifiers) and
123    /// [`publish_signers`](Self::publish_signers) because a reload must be able
124    /// to fail *after* building all three and still leave the running generation
125    /// untouched. Everything fallible is here; everything published is there.
126    ///
127    /// May block: `RelaySigner::from_config` contacts the upstream the first
128    /// time it is built for an account with no `kid` sidecar yet, which is why
129    /// `server::supervisor::supervise_reloads` runs this on a blocking thread.
130    pub fn build_parts(
131        &self,
132        resolved: &[config::ProfileConfig],
133        config: &Config,
134    ) -> anyhow::Result<GenerationParts> {
135        // Resolved before anything can dial: a proxy URL that cannot be
136        // understood must stop the process, and the `relay` backend below makes
137        // a real network call on its very first startup.
138        let egress = Arc::new(Egress::from_config(config)?);
139        // Built before the signer backends: the `relay` backend's background
140        // completion task has no `Profile`/`AppState` to reach a notifier
141        // through (it outlives any single request, the same reason it is handed
142        // `database`), so it is instead handed the whole `profile name ->
143        // dispatcher` map and looks up the right one by `Order.profile` once an
144        // issuance settles.
145        let mut dispatchers = notify::build_registry(resolved, egress.outbound(), &self.jobs)?;
146        // The process-wide web-admin security dispatcher, registered under a
147        // reserved key that no profile name can collide with. Built only when
148        // the panel is on; `NotifyJob` routes a `notify_deliver` row naming it
149        // here with no special case, and a reload republishes it in this same
150        // map.
151        if config.admin.enabled {
152            dispatchers.insert(
153                notify::ADMIN_DISPATCHER_KEY.to_string(),
154                notify::from_config(
155                    notify::ADMIN_DISPATCHER_KEY,
156                    &config.admin.notify,
157                    egress.outbound(),
158                    &self.jobs,
159                )?,
160            );
161        }
162        let signer_parts = signer::SignerParts {
163            database: self.database.clone(),
164            notifiers: self.notifiers.clone(),
165            metrics: self.metrics.clone(),
166            egress: egress.clone(),
167            jobs: self.jobs.clone(),
168        };
169        let previous = self
170            .signers
171            .lock()
172            .unwrap_or_else(std::sync::PoisonError::into_inner)
173            .clone();
174        // The worker's alone. Every other role serves from the read sides below
175        // and queues what needs a key, so it never reads `ca.key`, never logs
176        // in to a PKCS#11 token and never registers with a relay's upstream —
177        // which is also what makes the worker the one process that generates
178        // first-run material.
179        let signers = if self.roles.has(super::ProcessRole::Worker) {
180            signer::build_backends(resolved, &signer_parts, &previous)?
181        } else {
182            signer::SignerSet::default()
183        };
184        // After the backends, so that on a fresh directory the one that
185        // generates a CA has written its certificate before its read side
186        // looks for it. A process without the worker role finding none is
187        // refused by name: it was started before the process that makes it.
188        let previous = self
189            .infos
190            .lock()
191            .unwrap_or_else(std::sync::PoisonError::into_inner)
192            .clone();
193        let infos = signer::build_infos(resolved, &signer_parts, &previous)?;
194
195        Ok(GenerationParts {
196            egress,
197            dispatchers,
198            signers,
199            infos,
200        })
201    }
202
203    /// Makes `dispatchers` the generation every long-lived reader sees.
204    ///
205    /// Synchronous, and deliberately: it is one of the sends a reload makes
206    /// back-to-back so no task can observe a half-swapped generation.
207    pub fn publish_notifiers(&self, dispatchers: notify::DispatcherMap) {
208        self.notifiers_tx.send_replace(Arc::new(dispatchers));
209    }
210
211    /// Records `signers` as what the *next* reload compares against, and drops
212    /// whatever the generation before it held.
213    ///
214    /// That drop is the point at which a backend nobody references any more —
215    /// an unmounted profile's, or the instance a `[signer]` edit replaced — is
216    /// finally released. Deliberately after its replacement was built and has
217    /// adopted its state, never before.
218    pub fn publish_signers(
219        &self,
220        signers: signer::SignerSet,
221        infos: signer::SignerSet<dyn signer::SignerInfo>,
222    ) {
223        *self
224            .signers
225            .lock()
226            .unwrap_or_else(std::sync::PoisonError::into_inner) = signers;
227        *self
228            .infos
229            .lock()
230            .unwrap_or_else(std::sync::PoisonError::into_inner) = infos;
231    }
232}