1use axum::extract::ConnectInfo;
6use axum::http::{HeaderName, HeaderValue, Request, Version};
7use axum::routing::get;
8use futures_util::stream::StreamExt;
9use std::net::{IpAddr, SocketAddr};
10use std::time::Duration;
11use tower::ServiceBuilder;
12use tower_http::timeout::TimeoutLayer;
13use tracing::{Instrument, Span};
14
15use crate::middleware::{ServiceKind, apply_common_middleware};
16use crate::tcp::Sni;
17use anyhow::anyhow;
18use axum::Router;
19use axum::body::Body;
20use axum::handler::Handler;
21use axum::response::Response;
22use base64::{Engine as B64Engine, engine::general_purpose::URL_SAFE_NO_PAD as b64};
23use blake2::{
24 Blake2bVar,
25 digest::{Update, VariableOutput},
26};
27use bytes::Bytes;
28use http_body_util::Full;
29use hyper::header::{AUTHORIZATION, COOKIE, PROXY_AUTHORIZATION, SET_COOKIE};
30use hyper::{HeaderMap, StatusCode, Uri, header};
31use ordinary_config::RedactedHashAlg;
32use rcgen::{CertifiedKey, generate_simple_self_signed};
33use rustls_acme::{AcmeState, EventError, EventOk};
34use std::any::Any;
35use std::fmt;
36use std::fmt::{Debug, Display};
37use std::fs::File;
38use std::io::Write;
39use std::path::Path;
40use std::sync::Arc;
41use tokio::sync::watch::{Receiver, Sender};
42use tokio_rustls::{
43 rustls::ServerConfig,
44 rustls::pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject},
45};
46use tracing::field::DisplayValue;
47use valuable::{Mappable, Valuable, Value, Visit};
48
49pub const X_REQUEST_ID: HeaderName = HeaderName::from_static("x-request-id");
50pub const X_CORRELATION_ID: HeaderName = HeaderName::from_static("x-correlation-id");
51pub const REPORTING_ENDPOINTS: HeaderName = HeaderName::from_static("reporting-endpoints");
52pub const X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for");
53pub const X_FORWARDED_HOST: HeaderName = HeaderName::from_static("x-forwarded-host");
54pub const X_FORWARDED_PROTO: HeaderName = HeaderName::from_static("x-forwarded-proto");
55
56#[derive(PartialEq, Clone)]
57pub enum ProvisionMode {
58 Localhost,
59 Staging,
60 Production,
61}
62
63#[derive(Clone)]
64pub enum SecurityMode<T: AsRef<Path>> {
65 Insecure,
66 Secure(T, ProvisionMode),
67}
68
69pub struct WrappedRedactedHashingAlg(pub RedactedHashAlg);
70
71impl WrappedRedactedHashingAlg {
72 fn hash(&self, header_value: &str) -> String {
73 let span = tracing::info_span!("redacted:hash");
74
75 span.in_scope(|| match self.0 {
76 RedactedHashAlg::Blake2 => {
77 let mut out = [0u8; 32];
78
79 let mut hasher = match Blake2bVar::new(32) {
80 Ok(v) => v,
81 Err(err) => {
82 tracing::error!(%err);
83 return "redacted".into();
84 }
85 };
86
87 hasher.update(header_value.as_bytes());
88 if let Err(err) = hasher.finalize_variable(&mut out) {
89 tracing::error!(%err);
90 return "redacted".into();
91 }
92
93 b64.encode(&out[0..6])
94 }
95 RedactedHashAlg::Blake3 => {
96 b64.encode(&blake3::hash(header_value.as_bytes()).as_bytes()[0..6])
97 }
98 })
99 }
100}
101pub struct HeadersDebug<'a>(
102 pub &'a HeaderMap,
103 pub Arc<Option<WrappedRedactedHashingAlg>>,
104);
105
106#[cfg(tracing_unstable)]
107impl Valuable for HeadersDebug<'_> {
108 fn as_value(&self) -> Value<'_> {
109 Value::Mappable(self)
110 }
111
112 fn visit(&self, visit: &mut dyn Visit) {
113 for (k, v) in self.0 {
114 if let Ok(v) = v.to_str() {
115 if k == AUTHORIZATION || k == PROXY_AUTHORIZATION || k == COOKIE || k == SET_COOKIE
116 {
117 if let Some(hasher) = &*self.1 {
118 visit.visit_entry(k.as_str().as_value(), hasher.hash(v).as_value());
119 } else {
120 visit.visit_entry(k.as_str().as_value(), "redacted".as_value());
121 }
122 } else {
123 visit.visit_entry(k.as_str().as_value(), v.as_value());
124 }
125 }
126 }
127 }
128}
129
130#[cfg(tracing_unstable)]
131impl Mappable for HeadersDebug<'_> {
132 fn size_hint(&self) -> (usize, Option<usize>) {
133 self.0.iter().size_hint()
134 }
135}
136
137impl Debug for HeadersDebug<'_> {
138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139 use std::fmt::Write;
140
141 f.write_char('{')?;
142
143 let mut is_first = true;
144
145 for (k, v) in self.0 {
146 if let Ok(v) = v.to_str() {
147 if is_first {
148 is_first = false;
149 f.write_char('"')?;
150 } else {
151 f.write_str(",\"")?;
152 }
153
154 f.write_str(k.as_str())?;
155 f.write_str("\":\"")?;
156
157 if k == AUTHORIZATION || k == PROXY_AUTHORIZATION || k == COOKIE || k == SET_COOKIE
158 {
159 f.write_str("redacted")?;
160 f.write_char('"')?;
161 } else {
162 f.write_str(v)?;
163 f.write_char('"')?;
164 }
165 }
166 }
167
168 f.write_char('}')
169 }
170}
171
172#[must_use]
173pub fn get_http_version_str(v: Version) -> &'static str {
174 match v {
175 Version::HTTP_09 => "0.9",
176 Version::HTTP_10 => "1.0",
177 Version::HTTP_11 => "1.1",
178 Version::HTTP_2 => "2.0",
179 Version::HTTP_3 => "3.0",
180 _ => unreachable!(),
181 }
182}
183
184#[must_use]
185pub fn get_display_ip(log_ips: bool, req: &Request<Body>) -> Option<DisplayValue<IpAddr>> {
186 log_ips
187 .then(|| {
188 req.extensions()
189 .get::<ConnectInfo<SocketAddr>>()
190 .map(|addr| tracing::field::display(get_mapped_ip_for_addr(&addr.0)))
191 })
192 .flatten()
193}
194
195#[must_use]
196pub fn get_mapped_ip_for_addr(addr: &SocketAddr) -> IpAddr {
197 let ip = addr.ip();
198
199 if let IpAddr::V6(ipv6) = &ip
200 && let Some(ipv4) = ipv6.to_ipv4_mapped()
201 {
202 IpAddr::V4(ipv4)
203 } else {
204 ip
205 }
206}
207
208pub fn get_bearer_token_as_bytes(headers: &HeaderMap) -> anyhow::Result<Vec<u8>> {
209 if let Some(auth_header) = headers.get("authorization")
210 && let Ok(str_val) = auth_header.to_str()
211 && let Some(b64_token) = str_val.strip_prefix("Bearer ")
212 && let Ok(token) = b64.decode(b64_token)
213 {
214 Ok(token)
215 } else {
216 Err(anyhow!("failed to get token as bytes"))
217 }
218}
219
220pub fn get_host_fwd(headers: &HeaderMap, uri: &Uri, sni: Option<&Sni>) -> Option<String> {
221 if let Some(forwarded_values) = headers.get(header::FORWARDED)
222 && let Ok(forwarded_values_str) = forwarded_values.to_str()
223 && let Some(first_value) = forwarded_values_str.split(',').next()
224 && let Some(host) = first_value.split(';').find_map(|pair| {
225 let (key, value) = pair.split_once('=')?;
226 key.trim()
227 .eq_ignore_ascii_case("host")
228 .then(|| value.trim().trim_matches('"'))
229 })
230 {
231 return Some(host.to_owned());
232 }
233
234 if let Some(host) = headers
235 .get(X_FORWARDED_HOST)
236 .and_then(|host| host.to_str().ok())
237 {
238 return Some(host.to_owned());
239 }
240
241 if let Some(host) = headers
242 .get(header::HOST)
243 .and_then(|host| host.to_str().ok())
244 {
245 return Some(host.to_owned());
246 }
247
248 if let Some(sni) = sni {
249 return Some(sni.0.clone());
250 }
251
252 if let Some(authority) = uri.authority() {
253 return authority.as_str().rsplit('@').next().map(ToOwned::to_owned);
254 }
255
256 None
257}
258
259pub struct LatencyDisplay(pub f64);
260
261impl Display for LatencyDisplay {
262 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263 let mut t = self.0;
264
265 for unit in ["ns", "µs", "ms", "s"] {
266 if t < 10.0 {
267 return write!(f, "{t:.2}{unit}");
268 } else if t < 100.0 {
269 return write!(f, "{t:.1}{unit}");
270 } else if t < 1000.0 {
271 return write!(f, "{t:.0}{unit}");
272 }
273 t /= 1000.0;
274 }
275 write!(f, "{:.0}s", t * 1000.0)
276 }
277}
278
279#[allow(clippy::needless_pass_by_value)]
280pub fn response_for_panic(_: Box<dyn Any + Send + 'static>) -> Response<Full<Bytes>> {
281 #[allow(clippy::declare_interior_mutable_const)]
282 const TEXT_PLAIN: HeaderValue = HeaderValue::from_static("text/plain; charset=utf-8");
283
284 let mut res = Response::new(Full::new(Bytes::from_static(b"500 Internal Server Error")));
285
286 *res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
287 res.headers_mut().insert(header::CONTENT_TYPE, TEXT_PLAIN);
288
289 res
290}
291
292pub fn rustls_server_config(
293 key: impl AsRef<Path>,
294 cert: impl AsRef<Path>,
295) -> anyhow::Result<Arc<ServerConfig>> {
296 let key = PrivateKeyDer::from_pem_file(key)?;
297
298 let certs = CertificateDer::pem_file_iter(cert)?.flatten().collect();
299
300 let mut config = ServerConfig::builder()
301 .with_no_client_auth()
302 .with_single_cert(certs, key)?;
303
304 config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
305
306 Ok(Arc::new(config))
307}
308
309pub fn generate_self_signed_localhost_certs(cert_dir_path: impl AsRef<Path>) -> anyhow::Result<()> {
311 std::fs::create_dir_all(&cert_dir_path)?;
312
313 let cert_path = cert_dir_path.as_ref().join("crt.pem");
314 let key_path = cert_dir_path.as_ref().join("key.pem");
315
316 if !cert_path.exists() || !key_path.exists() {
317 let subject_alt_names = vec!["localhost".to_string()];
318
319 let CertifiedKey { cert, signing_key } =
320 match generate_simple_self_signed(subject_alt_names) {
321 Ok(ck) => {
322 tracing::info!("generated self-signed localhost cert");
323 ck
324 }
325 Err(err) => {
326 tracing::error!("failed to generate self-signed localhost cert");
327 return Err(err.into());
328 }
329 };
330
331 let cert = cert.pem();
332 let key = signing_key.serialize_pem();
333
334 let mut cert_file = File::create(cert_path)?;
335 let mut key_file = File::create(key_path)?;
336
337 cert_file.write_all(cert.as_bytes())?;
338 cert_file.flush()?;
339 key_file.write_all(key.as_bytes())?;
340 key_file.flush()?;
341 }
342
343 Ok(())
344}
345
346pub fn acme_task(
347 acme_span_clone: Span,
348 mut state: AcmeState<std::io::Error>,
349 signal_tx: Sender<()>,
350 mut terminate_rx: Option<Receiver<bool>>,
351) {
352 tokio::spawn(async move {
353 async {
354 tracing::info!("ready");
355
356 loop {
357 let event = if let Some(terminate_rx) = terminate_rx.as_mut() {
358 tokio::select! {
359 state = state.next() => state,
360 () = signal_tx.closed() => {
361 tracing::warn!("not accepting new connections");
362 break;
363 },
364 _ = terminate_rx.changed() => {
365 tracing::warn!("not accepting new connections");
366 break;
367 }
368 }
369 } else {
370 tokio::select! {
371 state = state.next() => state,
372 () = signal_tx.closed() => {
373 tracing::warn!("not accepting new connections");
374 break;
375 }
376 }
377 };
378
379 if let Some(event) = event {
380 match event {
381 Ok(evt) => match evt {
382 EventOk::DeployedNewCert => {
383 tracing::info!(evt.deploy = %"new", "cert");
384 }
385 EventOk::CertCacheStore => {
386 tracing::info!(evt.cache = %"stored", "cert");
387 }
388 EventOk::AccountCacheStore => {
389 tracing::info!(evt.cache = %"stored", "account");
390 }
391 EventOk::DeployedCachedCert => {
392 tracing::info!(evt.deploy = %"cached", "cert");
393 }
394 },
395 Err(err) => match err {
396 EventError::AccountCacheStore(err) => {
397 tracing::error!(%err, evt.cache = %"store", "account");
398 }
399 EventError::CertCacheStore(err) => {
400 tracing::error!(%err, evt.cache = %"store", "cert");
401 }
402 EventError::AccountCacheLoad(err) => {
403 tracing::error!(%err, evt.cache = %"load", "account");
404 }
405 EventError::CachedCertParse(err) => {
406 tracing::error!(%err, evt.parse = %"cache", "cert");
407 }
408 EventError::NewCertParse(err) => {
409 tracing::error!(%err, evt.parse = %"new", "cert");
410 }
411 EventError::CertCacheLoad(err) => {
412 tracing::error!(%err, evt.cache = %"load", "cert");
413 }
414 EventError::Order(err) => {
415 tracing::error!(%err, "order");
416 }
417 },
418 }
419 } else {
420 break;
421 }
422 }
423 }
424 .instrument(acme_span_clone)
425 .await;
426 });
427}
428
429#[allow(clippy::too_many_lines, clippy::too_many_arguments)]
430pub fn redirect_service<H, T, S>(
431 span_clone: &Span,
432 redacted_hash: Arc<Option<WrappedRedactedHashingAlg>>,
433 log_ips: bool,
434 log_headers: bool,
435 handler: H,
436 state: S,
437 api_domain: Option<String>,
438) -> Router
439where
440 H: Handler<T, S>,
441 T: 'static,
442 S: Clone + Send + Sync + 'static,
443{
444 let router = Router::new()
445 .route("/healthz", get(|| async { StatusCode::OK }))
446 .fallback(handler);
447
448 apply_common_middleware(
449 router,
450 &state,
451 Some(span_clone),
452 String::new(),
453 log_headers,
454 log_ips,
455 redacted_hash,
456 ServiceKind::Redirect,
457 Some(api_domain.unwrap_or("redirect".to_owned())),
458 )
459 .layer(ServiceBuilder::new().layer(TimeoutLayer::with_status_code(
460 StatusCode::REQUEST_TIMEOUT,
461 Duration::from_secs(5),
462 )))
463}