1pub mod acme;
9pub mod auth;
10pub mod config;
11pub mod cp;
12pub mod generate;
13pub mod limiter;
14pub mod metrics;
15pub mod proxy;
16pub mod reload;
17pub mod supervisor;
18pub mod tls;
19pub mod waf;
20
21use std::num::NonZeroU32;
22use std::sync::Arc;
23
24use anyhow::{Context, Result};
25use arc_swap::ArcSwap;
26use axum::{
27 extract::DefaultBodyLimit,
28 routing::{any, get, post},
29 Router,
30};
31use governor::{Quota, RateLimiter};
32use hyper_util::client::legacy::Client;
33use hyper_util::rt::TokioExecutor;
34
35use crate::auth::AuthEngine;
36use crate::config::{parse_duration, parse_rate, parse_size, Config};
37use crate::metrics::Metrics;
38use crate::proxy::{
39 csp_report, metrics_handler, ready, AppState, RouteLimiter, Runtime, StrLimiter,
40};
41
42pub use crate::auth::hash_password;
43
44fn quota(rate: &str, burst: u32) -> Result<Quota> {
48 let (count, period) = parse_rate(rate)?;
49 anyhow::ensure!(count > 0, "rate count must be > 0 (got \"{rate}\")");
50 anyhow::ensure!(burst > 0, "burst must be > 0 (rate \"{rate}\")");
51 let per_cell = period / count;
53 let burst = NonZeroU32::new(burst).unwrap();
54 Ok(Quota::with_period(per_cell)
55 .context("rate too high for a usable replenish interval")?
56 .allow_burst(burst))
57}
58
59pub fn build_runtime(cfg: Arc<Config>) -> Result<Runtime> {
65 let rl = &cfg.ratelimit;
66
67 let store_mode = crate::limiter::StoreMode::parse(&rl.store)?;
72 let use_distributed = rl.enabled && store_mode.is_distributed();
73
74 let distributed = if use_distributed {
75 Some(crate::limiter::DistributedLimiter::build(rl, store_mode)?)
76 } else {
77 None
78 };
79
80 let build_local = rl.enabled && !use_distributed;
82
83 let ip_limiter = if build_local {
84 Some(Arc::new(RateLimiter::keyed(quota(&rl.rate, rl.burst)?)))
85 } else {
86 None
87 };
88
89 let mut route_limiters = Vec::new();
90 if build_local {
91 for route in &rl.routes {
92 anyhow::ensure!(
93 !route.path.is_empty(),
94 "ratelimit.routes[].path must not be empty"
95 );
96 route_limiters.push(RouteLimiter {
97 prefix: route.path.clone(),
98 limiter: Arc::new(RateLimiter::keyed(quota(&route.rate, route.burst)?)),
99 });
100 }
101 }
102
103 let key_limiter: Option<Arc<StrLimiter>> = if build_local && rl.per_key.enabled {
104 Some(Arc::new(RateLimiter::keyed(quota(
105 &rl.per_key.rate,
106 rl.per_key.burst,
107 )?)))
108 } else {
109 None
110 };
111
112 let auth = AuthEngine::build(&cfg.auth)?;
113 let waf = crate::waf::WafEngine::build(&cfg.waf)?;
116
117 let max_body = parse_size(&cfg.validation.max_body)?;
118 let max_response_body = parse_size(&cfg.validation.max_response_body)?;
119 let max_header_bytes = parse_size(&cfg.validation.max_header_bytes)?;
120 let upstream_timeout = parse_duration(&cfg.validation.upstream_timeout)?;
122 let upstream_timeout = (!upstream_timeout.is_zero()).then_some(upstream_timeout);
123
124 Ok(Runtime {
125 upstream_base: Arc::new(cfg.upstream_base()),
126 auth,
127 waf,
128 distributed,
129 ip_limiter,
130 route_limiters,
131 key_limiter,
132 max_body,
133 max_response_body,
134 max_header_bytes,
135 upstream_timeout,
136 stream_passthrough: cfg.validation.stream_passthrough,
137 cfg,
138 })
139}
140
141pub fn build_state(cfg: Arc<Config>) -> Result<AppState> {
144 let cp = crate::cp::CpClient::from_cfg(&cfg.control_plane)?;
146 let runtime = build_runtime(cfg)?;
147 let client =
148 Client::builder(TokioExecutor::new()).build_http::<http_body_util::Full<bytes::Bytes>>();
149 Ok(AppState {
150 client,
151 metrics: Arc::new(Metrics::new()),
152 runtime: Arc::new(ArcSwap::from_pointee(runtime)),
153 cp,
154 })
155}
156
157pub fn build_router(state: AppState) -> Router {
164 public_routes()
165 .merge(admin_routes())
166 .layer(DefaultBodyLimit::disable())
167 .with_state(state)
168}
169
170pub fn build_public_router(state: AppState) -> Router {
174 public_routes()
175 .layer(DefaultBodyLimit::disable())
176 .with_state(state)
177}
178
179pub fn build_admin_router(state: AppState) -> Router {
184 admin_routes().with_state(state)
185}
186
187fn public_routes() -> Router<AppState> {
190 Router::new()
191 .route(
192 "/__edgeguard/csp-report",
193 post(csp_report).layer(DefaultBodyLimit::max(64 * 1024)),
194 )
195 .fallback(any(proxy::handle))
196}
197
198fn admin_routes() -> Router<AppState> {
200 Router::new()
201 .route("/__edgeguard/health", get(|| async { "ok" }))
202 .route("/__edgeguard/ready", get(ready))
203 .route("/__edgeguard/metrics", get(metrics_handler))
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209 use crate::config::RateLimitCfg;
210
211 fn cfg_with_ratelimit(rate: &str, burst: u32) -> Config {
212 Config {
213 ratelimit: RateLimitCfg {
214 enabled: true,
215 rate: rate.into(),
216 burst,
217 ..Default::default()
218 },
219 ..Default::default()
220 }
221 }
222
223 #[test]
224 fn build_state_rejects_zero_rate() {
225 assert!(build_state(Arc::new(cfg_with_ratelimit("0/min", 20))).is_err());
228 }
229
230 #[test]
231 fn build_state_rejects_zero_burst() {
232 assert!(build_state(Arc::new(cfg_with_ratelimit("60/min", 0))).is_err());
233 }
234
235 #[test]
236 fn build_runtime_builds_route_and_key_limiters() {
237 let mut cfg = Config::default();
238 cfg.ratelimit.routes = vec![crate::config::RouteRateLimit {
239 path: "/api/".into(),
240 rate: "10/sec".into(),
241 burst: 5,
242 }];
243 cfg.ratelimit.per_key = crate::config::PerKeyRateLimit {
244 enabled: true,
245 rate: "1000/hour".into(),
246 burst: 100,
247 };
248 let rt = build_runtime(Arc::new(cfg)).unwrap();
249 assert_eq!(rt.route_limiters.len(), 1);
250 assert_eq!(rt.route_limiters[0].prefix, "/api/");
251 assert!(rt.key_limiter.is_some());
252 }
253
254 #[test]
255 fn build_runtime_rejects_bad_route_rate() {
256 let mut cfg = Config::default();
257 cfg.ratelimit.routes = vec![crate::config::RouteRateLimit {
258 path: "/api/".into(),
259 rate: "0/sec".into(),
260 burst: 5,
261 }];
262 assert!(build_runtime(Arc::new(cfg)).is_err());
263 }
264}