acme_proxy/lib.rs
1//! ACME (RFC 8555) Server Implementation
2//!
3//! This is a server-side implementation of the ACME protocol (RFC 8555) for
4//! issuing and managing SSL/TLS certificates. It serves as a backend for
5//! certificate clients like certbot and acme.sh.
6//!
7//! ## Features
8//!
9//! - The full RFC 8555 flow: directory, newNonce, newAccount, account
10//! lookup/update and deactivation, newOrder, authorizations and challenges,
11//! finalize, certificate retrieval via signed POST-as-GET, and revocation
12//! - JWS signature verification for EC (ES256) and RSA (RS256) keys
13//! - Automatic nonce management with replay protection
14//! - Challenge validation behind pluggable validators (`http-01`, `dns-01`,
15//! `tls-alpn-01`), with a configurable bypass
16//! - Certificate issuance behind a pluggable signer backend: a local CA (whose
17//! key may live in a PKCS#11 token), a relay to an upstream ACME CA, or an
18//! operator-supplied script
19//! - **Profiles** — several independent ACME endpoints in one process, each with
20//! its own signer, filters, challenge validators and EAB policy
21//! - External Account Binding (§7.3.4), account key rollover (§7.3.5) and
22//! Renewal Information (RFC 9773)
23//! - Access control behind a policy engine of named checks combined by boolean
24//! rules, including an IPAM lookup (NetBox, phpIPAM or a script) asking the
25//! inventory whether the client's own address owns the names it is requesting
26//! - An append-only audit trail of every issuance *and every refusal*
27//! - An optional web admin listener, and admin subcommands in the same binary
28//! - Optional Prometheus metrics on a third listener of their own
29//! - A durable job queue, so work the server owes itself survives a restart and
30//! an upstream blip is retried rather than invalidating a client's order
31//! - Configuration reload on `SIGHUP` — a rebuild and a swap, with
32//! `database.url` the only key that still needs a restart
33//! - `SQLite` or `PostgreSQL` persistence for accounts, nonces, orders and the
34//! audit trail, and `acme-proxy transfer` to move between them
35//! - Configurable via TOML, environment variables, or defaults
36//!
37//! ## Architecture
38//!
39//! One binary over a workspace of library crates, each naming only the crates
40//! beneath it — so the layering is the compiler's to enforce. Bottom-up:
41//!
42//! - [`acme_proxy_core`] - Configuration, the ACME wire types (identifiers,
43//! JWS, problem documents, routes), certificate parsing, EAB and key-change
44//! verification, and the audit trail's vocabulary
45//! - [`acme_proxy_store`] - The storage layer over `SQLite` or `PostgreSQL`, one
46//! module per table, and both embedded migration sets
47//! - [`acme_proxy_net`] - DNS, outbound HTTP and forward proxies, TLS, the
48//! listeners, and the challenge validators (http-01, dns-01, tls-alpn-01)
49//! - [`acme_proxy_policy`] - The filter engine (who may ask for what) and the
50//! IPAM inventories one of its checks consults
51//! - [`acme_proxy_jobs`] - The durable job queue, the notifications delivered
52//! through it, the audit writer and the Prometheus metrics
53//! - [`acme_proxy_signer`] - The signing backends (local CA, ACME relay, custom
54//! script) and their read side
55//! - [`acme_proxy_protocol`] - The ACME services, the extractors that verify a
56//! signed request before a handler runs, the handlers, the middlewares, and
57//! the routers
58//! - [`acme_proxy_admin`] - The operation layer both front ends dispatch to,
59//! and the web admin panel over it
60//! - [`acme_proxy_server`] - The runtime: role processes, listeners,
61//! configuration reload and logging
62//!
63//! This crate is the binary and its terminal front end: [`cli`], the `clap`
64//! command tree.
65//!
66//! ## Usage
67//!
68//! The main entry point is `router::build_app()`, which mounts one ACME router
69//! per configured profile under `/profile/<name>` and serves the server-level
70//! routes (`/health`) at the root.
71//!
72//! ```rust,no_run
73//! use std::net::SocketAddr;
74//! use std::sync::Arc;
75//! use acme_proxy_protocol::profile::{Profile, ProfileParts};
76//! use acme_proxy_protocol::router::build_app;
77//! use acme_proxy_store::db::Database;
78//! use acme_proxy_signer as signer;
79//! use acme_proxy_jobs::{jobs, notify};
80//! use acme_proxy_policy::{filter, ipam};
81//! use acme_proxy_net::challenge;
82//! use acme_proxy_core::config::Config;
83//!
84//! #[tokio::main]
85//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
86//! let config = Arc::new(Config::load()?);
87//! let database = Arc::new(Database::connect_and_migrate(&config.database.url).await?);
88//!
89//! let resolved = config.resolve_profiles()?;
90//! // The resolver and the proxy policy, resolved before anything can dial:
91//! // a proxy URL that cannot be understood must stop the process rather
92//! // than leave egress elsewhere, and `dns.resolver` governs every outbound
93//! // connection this server makes, not just challenge lookups. Bundled,
94//! // because every outbound client takes them together — and because the
95//! // rendering beside them is what tells a reload whether a signer backend
96//! // has to be rebuilt.
97//! let egress = Arc::new(acme_proxy_net::egress::Egress::from_config(&config)?);
98//! let outbound = egress.outbound();
99//! // The enqueue side of the durable queue, built first because everything
100//! // below queues into it. A backend that defers issuance (`relay`) is
101//! // handed one at construction, and so is every notify dispatcher — a
102//! // notification is a job row too. The runner that drains it is started
103//! // separately, below.
104//! let job_queue = jobs::JobQueue::new(database.clone(), &config.jobs);
105//! // Built once, up front: an asynchronous signer backend (`relay`)
106//! // has no `Profile` to reach a notifier through from its background
107//! // completion task, so it is handed this whole map instead — and so is
108//! // the `NotifyJob` that performs the deliveries.
109//! let mut notifiers = std::collections::HashMap::new();
110//! for profile in &resolved {
111//! notifiers.insert(
112//! profile.name.clone(),
113//! notify::from_config(
114//! &profile.name,
115//! &profile.sections.notify,
116//! outbound.clone(),
117//! &job_queue,
118//! )?,
119//! );
120//! }
121//! let notifiers = Arc::new(notifiers);
122//! // The Prometheus counters. Built here rather than per generation, so a
123//! // `SIGHUP` does not reset every counter to zero — see `Assembly`.
124//! let metrics = Arc::new(acme_proxy_jobs::metrics::Metrics::new(database.clone()));
125//!
126//! let mut profiles = Vec::new();
127//! // Each profile's signer in two halves: the backend, which holds the key
128//! // and is handed only to the job handlers below, and its read side, which
129//! // is all a profile — and so a request — ever sees.
130//! let mut backends = Vec::new();
131//! for profile in &resolved {
132//! let sections = &profile.sections;
133//! let backend = signer::from_config(
134//! §ions.signer,
135//! &signer::SignerParts {
136//! database: database.clone(),
137//! notifiers: notifiers.clone().into(),
138//! metrics: metrics.clone(),
139//! egress: egress.clone(),
140//! jobs: job_queue.clone(),
141//! },
142//! )?;
143//! backends.push((profile.name.clone(), backend.clone()));
144//! profiles.push(Arc::new(Profile::new(
145//! &profile.name,
146//! &config.server.base_url,
147//! ProfileParts {
148//! signer_info: backend.info(),
149//! filter: filter::from_config(
150//! §ions.filter,
151//! &config.dns,
152//! ipam::from_config(§ions.ipam, outbound.clone())?,
153//! sections.eab.enabled,
154//! )?,
155//! challenges: challenge::from_config(
156//! §ions.challenge,
157//! &config.dns,
158//! egress.proxies.clone(),
159//! )?,
160//! order: sections.order.clone(),
161//! eab: sections.eab.clone(),
162//! meta: sections.meta.clone(),
163//! notify: notifiers[&profile.name].clone(),
164//! },
165//! )));
166//! }
167//! // Process-wide, like `[audit]` itself: one trail for the whole CA,
168//! // shared by every profile's router and by the web admin listener.
169//! // The registry is a parameter rather than a builder step, so a serving
170//! // process cannot build an auditor that counts into nothing. The counters
171//! // come off the same `AuditRecord` the trail is written from, so the two
172//! // can never disagree.
173//! let audit = Arc::new(acme_proxy_jobs::auditor::Auditor::from_config(
174//! &config.audit,
175//! &config.dns,
176//! database.clone(),
177//! metrics.clone(),
178//! )?);
179//! // The queue goes in too: `POST /chall/{id}` claims a challenge and
180//! // queues its validation rather than performing it inside the request.
181//! let app = build_app(
182//! database.clone(),
183//! config.clone(),
184//! profiles,
185//! audit.clone(),
186//! metrics.clone(),
187//! job_queue.clone(),
188//! );
189//!
190//! // One runner drains the queue for the process. Every handler comes from
191//! // a subsystem that has background work — relayed issuance, notification
192//! // delivery, the periodic table sweeps — and the runner calls `recover`
193//! // on each before it claims anything, which is how work a previous run
194//! // left in flight is picked back up, and how each sweep's single row gets
195//! // queued. **One handler per kind, never per backend**: a handler covers
196//! // every profile or backend of its kind and picks the right one per row,
197//! // since `register` refuses a second handler for a kind it already has.
198//! let mut registry = jobs::JobRegistry::new();
199//! // `finalize` queues the signing: this is the one handler that asks a
200//! // backend to issue, and the one place a backend is handed out.
201//! registry.register(Arc::new(acme_proxy_protocol::acme::issue::SignerIssueJob::new(
202//! database.clone(),
203//! audit,
204//! backends,
205//! notifiers.clone().into(),
206//! )))?;
207//! registry.register(Arc::new(notify::NotifyJob::new(notifiers)))?;
208//! registry.register(Arc::new(jobs::SweepJob::nonces(
209//! database.clone(),
210//! std::time::Duration::from_secs(config.nonce.ttl_seconds),
211//! )))?;
212//! let (_shutdown, shutdown_rx) = tokio::sync::watch::channel(false);
213//! jobs::spawn_runner(job_queue, Arc::new(registry), &config.jobs, shutdown_rx);
214//!
215//! let listener = tokio::net::TcpListener::bind(&config.server.bind_address).await?;
216//! axum::serve(listener, app.into_make_service_with_connect_info::<SocketAddr>()).await?;
217//!
218//! Ok(())
219//! }
220//! ```
221
222pub mod cli;
223
224// Re-export name shape helpers for backwards compatibility