This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / knot2 / crates / knot-edge / src / lib.rs
12 kB 391 lines
1mod acme; 2mod altsvc; 3mod compression; 4mod limits; 5mod peer; 6mod protocol; 7mod quic; 8mod robustness; 9mod tcp; 10mod tls; 11mod zerortt; 12 13use std::future::Future; 14use std::net::SocketAddr; 15use std::path::{Path, PathBuf}; 16use std::pin::Pin; 17use std::sync::Arc; 18 19use axum::Router; 20use axum::middleware::from_fn; 21use rustls::server::ResolvesServerCert; 22use tokio::net::TcpListener; 23use tokio_util::sync::CancellationToken; 24 25pub use acme::{AcmeCacheDir, AcmeContact, AcmeContactError, AcmeError, AcmeParams}; 26pub use limits::{ 27 ConnectionBudget, HeaderTimeout, IdleTimeout, ListenLimits, MaxConcurrentStreams, 28}; 29pub use peer::SocketPeer; 30pub use protocol::NegotiatedProtocol; 31pub use quic::EndpointError; 32pub use robustness::{ 33 BodyInactivityTimeout, BurstSize, EdgeGuards, MaxInflightRequests, RequestTimeout, 34 RequestsPerSecond, WriteRequestTimeout, 35}; 36pub use tls::{ReloadableCertResolver, SpkiPin, TlsError, load_certified_key}; 37pub use zerortt::{EarlyData, RequiresFullHandshake, ZeroRttRoutes, ZeroRttSafe}; 38 39pub mod fuzz { 40 pub fn spki_of_certificate(data: &[u8]) { 41 crate::tls::fuzz_of_certificate(data); 42 } 43 44 pub fn spki_pin(data: &[u8]) { 45 let _ = crate::SpkiPin::from_base64(&String::from_utf8_lossy(data)); 46 } 47} 48 49#[derive(Debug, Clone, PartialEq, Eq)] 50pub struct CertChainPath(PathBuf); 51 52impl CertChainPath { 53 pub fn new(path: impl Into<PathBuf>) -> Self { 54 Self(path.into()) 55 } 56 57 pub fn as_path(&self) -> &Path { 58 &self.0 59 } 60} 61 62#[derive(Debug, Clone, PartialEq, Eq)] 63pub struct PrivateKeyPath(PathBuf); 64 65impl PrivateKeyPath { 66 pub fn new(path: impl Into<PathBuf>) -> Self { 67 Self(path.into()) 68 } 69 70 pub fn as_path(&self) -> &Path { 71 &self.0 72 } 73} 74 75#[derive(Debug, Clone, PartialEq, Eq)] 76pub struct ClientCaPath(PathBuf); 77 78impl ClientCaPath { 79 pub fn new(path: impl Into<PathBuf>) -> Self { 80 Self(path.into()) 81 } 82 83 pub fn as_path(&self) -> &Path { 84 &self.0 85 } 86} 87 88#[derive(Debug, Clone, Copy, PartialEq, Eq)] 89pub struct PublicBind(SocketAddr); 90 91impl PublicBind { 92 pub const fn new(addr: SocketAddr) -> Self { 93 Self(addr) 94 } 95 96 pub const fn get(self) -> SocketAddr { 97 self.0 98 } 99} 100 101#[derive(Debug, Clone, Copy, PartialEq, Eq)] 102pub struct InternalBind(SocketAddr); 103 104impl InternalBind { 105 pub const fn new(addr: SocketAddr) -> Self { 106 Self(addr) 107 } 108 109 pub const fn get(self) -> SocketAddr { 110 self.0 111 } 112} 113 114pub struct StaticCertPaths { 115 pub cert_path: CertChainPath, 116 pub key_path: PrivateKeyPath, 117} 118 119pub enum CertSource { 120 Static(StaticCertPaths), 121 Acme(AcmeParams), 122} 123 124pub struct InternalTls { 125 pub addr: InternalBind, 126 pub client_ca_path: ClientCaPath, 127 pub spki_pin: SpkiPin, 128} 129 130pub struct TlsSetup { 131 pub source: CertSource, 132 pub http3: bool, 133 pub internal: Option<InternalTls>, 134} 135 136pub struct EdgeConfig { 137 pub http_addr: PublicBind, 138 pub limits: ListenLimits, 139 pub guards: EdgeGuards, 140 pub tls: Option<TlsSetup>, 141} 142 143#[derive(Debug, thiserror::Error)] 144pub enum EdgeError { 145 #[error(transparent)] 146 Io(#[from] std::io::Error), 147 #[error(transparent)] 148 Tls(#[from] TlsError), 149 #[error(transparent)] 150 Acme(#[from] AcmeError), 151 #[error(transparent)] 152 Endpoint(#[from] EndpointError), 153} 154 155type Served = Pin<Box<dyn Future<Output = Result<(), EdgeError>> + Send>>; 156 157fn base_router(app: RequiresFullHandshake, early_data_safe: ZeroRttRoutes) -> Router { 158 early_data_safe 159 .into_router() 160 .merge(app.into_router()) 161 .layer(compression::layer()) 162 .layer(from_fn(zerortt::tag_from_header)) 163} 164 165fn finish(router: Router) -> Router { 166 altsvc::with_host_from_authority(router.layer(from_fn(protocol::tag))) 167} 168 169pub async fn serve( 170 config: EdgeConfig, 171 app: RequiresFullHandshake, 172 early_data_safe: ZeroRttRoutes, 173 shutdown: CancellationToken, 174) -> Result<(), EdgeError> { 175 let EdgeConfig { 176 http_addr, 177 limits, 178 guards, 179 tls, 180 } = config; 181 let layers = guards.prepare(&shutdown); 182 let early_data = early_data_safe.early_data_policy(); 183 let wants_internal = tls 184 .as_ref() 185 .and_then(|setup| setup.internal.as_ref()) 186 .is_some(); 187 let base = base_router(app, early_data_safe); 188 let internal_router = wants_internal.then(|| finish(base.clone())); 189 let router = finish(robustness::apply(base, layers)); 190 let listener = TcpListener::bind(http_addr.get()).await?; 191 192 let Some(setup) = tls else { 193 return Ok(tcp::serve_plaintext(listener, router, limits, shutdown).await?); 194 }; 195 196 let (resolver, acme): (Arc<dyn ResolvesServerCert>, bool) = match setup.source { 197 CertSource::Static(paths) => { 198 let certified = tls::load_certified_key(&paths)?; 199 let reloadable = Arc::new(ReloadableCertResolver::new(certified)); 200 tls::spawn_cert_reload(Arc::clone(&reloadable), paths, shutdown.clone()); 201 (reloadable, false) 202 } 203 CertSource::Acme(params) => (acme::start(params, shutdown.clone())?, true), 204 }; 205 206 let extra_alpn: &[&[u8]] = if acme { &[tls::ACME_TLS_ALPN] } else { &[] }; 207 let tcp_config = Arc::new(tls::build_tls_server_config( 208 Arc::clone(&resolver), 209 extra_alpn, 210 )?); 211 let port = http_addr.get().port(); 212 213 let mut servers: Vec<Served> = Vec::new(); 214 215 servers.push({ 216 let app = match setup.http3 { 217 true => altsvc::with_alt_svc(router.clone(), altsvc::Port::new(port)), 218 false => router.clone(), 219 }; 220 let shutdown = shutdown.clone(); 221 Box::pin(async move { 222 let result = tcp::serve_tls(listener, app, tcp_config, limits, shutdown.clone()).await; 223 shutdown.cancel(); 224 Ok(result?) 225 }) 226 }); 227 228 if setup.http3 { 229 let endpoint = 230 quic::build_endpoint(http_addr.get(), Arc::clone(&resolver), limits, early_data)?; 231 let app = router.clone(); 232 let shutdown = shutdown.clone(); 233 servers.push(Box::pin(async move { 234 quic::serve_http3(endpoint, app, limits, shutdown.clone()).await; 235 shutdown.cancel(); 236 Ok(()) 237 })); 238 } 239 240 if let Some(internal) = setup.internal { 241 let internal_listener = TcpListener::bind(internal.addr.get()).await?; 242 let mtls_config = Arc::new(tls::build_mtls_server_config( 243 Arc::clone(&resolver), 244 &internal.client_ca_path, 245 internal.spki_pin, 246 )?); 247 let app = internal_router 248 .expect("an internal router is built whenever an internal bind is configured"); 249 let shutdown = shutdown.clone(); 250 servers.push(Box::pin(async move { 251 let result = tcp::serve_tls( 252 internal_listener, 253 app, 254 mtls_config, 255 limits, 256 shutdown.clone(), 257 ) 258 .await; 259 shutdown.cancel(); 260 Ok(result?) 261 })); 262 } 263 264 futures::future::join_all(servers) 265 .await 266 .into_iter() 267 .collect::<Result<Vec<()>, EdgeError>>() 268 .map(drop) 269} 270 271#[cfg(test)] 272mod tests { 273 use super::*; 274 275 use axum::body::Body; 276 use axum::extract::ConnectInfo; 277 use axum::routing::post; 278 use http::{Request, StatusCode}; 279 use tower::ServiceExt; 280 281 use std::num::{NonZeroU32, NonZeroU64}; 282 283 use zerortt::ZeroRttSafe; 284 285 fn test_layers() -> robustness::GuardLayers { 286 EdgeGuards::new( 287 RequestsPerSecond::new(NonZeroU32::new(10_000).unwrap()), 288 BurstSize::new(NonZeroU32::new(10_000).unwrap()), 289 MaxInflightRequests::new(NonZeroU32::new(1_024).unwrap()), 290 RequestTimeout::from_millis(NonZeroU64::new(30_000).unwrap()), 291 BodyInactivityTimeout::from_millis(NonZeroU64::new(30_000).unwrap()), 292 WriteRequestTimeout::from_millis(NonZeroU64::new(1_800_000).unwrap()), 293 knot_types::ProxyTrust::default(), 294 ) 295 .prepare(&CancellationToken::new()) 296 } 297 298 fn wired() -> Router { 299 let safe = 300 ZeroRttRoutes::new().get("/info/refs", ZeroRttSafe::new(|| async { "advertisement" })); 301 let full = RequiresFullHandshake::new( 302 Router::new().route("/git-upload-pack", post(|| async { "pack" })), 303 ); 304 finish(robustness::apply(base_router(full, safe), test_layers())) 305 } 306 307 fn tight_layers() -> robustness::GuardLayers { 308 EdgeGuards::new( 309 RequestsPerSecond::new(NonZeroU32::new(1).unwrap()), 310 BurstSize::new(NonZeroU32::new(2).unwrap()), 311 MaxInflightRequests::new(NonZeroU32::new(1_024).unwrap()), 312 RequestTimeout::from_millis(NonZeroU64::new(30_000).unwrap()), 313 BodyInactivityTimeout::from_millis(NonZeroU64::new(30_000).unwrap()), 314 WriteRequestTimeout::from_millis(NonZeroU64::new(1_800_000).unwrap()), 315 knot_types::ProxyTrust::default(), 316 ) 317 .prepare(&CancellationToken::new()) 318 } 319 320 async fn status_of(mut request: Request<Body>) -> StatusCode { 321 request 322 .extensions_mut() 323 .insert(ConnectInfo(SocketAddr::from(([127, 0, 0, 1], 41001)))); 324 wired().oneshot(request).await.unwrap().status() 325 } 326 327 #[tokio::test] 328 async fn a_write_in_early_data_is_refused_with_425() { 329 let request = Request::post("/git-upload-pack") 330 .header("early-data", "1") 331 .body(Body::empty()) 332 .unwrap(); 333 assert_eq!(status_of(request).await, StatusCode::TOO_EARLY); 334 } 335 336 #[tokio::test] 337 async fn a_write_after_the_handshake_is_served() { 338 let request = Request::post("/git-upload-pack") 339 .body(Body::empty()) 340 .unwrap(); 341 assert_eq!(status_of(request).await, StatusCode::OK); 342 } 343 344 #[tokio::test] 345 async fn the_advertisement_is_served_even_in_early_data() { 346 let request = Request::get("/info/refs") 347 .header("early-data", "1") 348 .body(Body::empty()) 349 .unwrap(); 350 assert_eq!(status_of(request).await, StatusCode::OK); 351 } 352 353 #[tokio::test] 354 async fn the_internal_admin_router_shares_no_rate_limit_budget_with_the_data_plane() { 355 let safe = ZeroRttRoutes::new().get("/info/refs", ZeroRttSafe::new(|| async { "ok" })); 356 let full = RequiresFullHandshake::new( 357 Router::new().route("/git-upload-pack", post(|| async { "pack" })), 358 ); 359 let base = base_router(full, safe); 360 let public = finish(robustness::apply(base.clone(), tight_layers())); 361 let internal = finish(base); 362 363 let request = || { 364 let mut request = Request::get("/info/refs").body(Body::empty()).unwrap(); 365 request 366 .extensions_mut() 367 .insert(ConnectInfo(SocketAddr::from(([127, 0, 0, 1], 41001)))); 368 request 369 }; 370 371 let p1 = public.clone().oneshot(request()).await.unwrap().status(); 372 let p2 = public.clone().oneshot(request()).await.unwrap().status(); 373 let p3 = public.clone().oneshot(request()).await.unwrap().status(); 374 assert_eq!([p1, p2], [StatusCode::OK, StatusCode::OK]); 375 assert_eq!( 376 p3, 377 StatusCode::TOO_MANY_REQUESTS, 378 "the public edge still enforces the per-IP burst" 379 ); 380 381 let i1 = internal.clone().oneshot(request()).await.unwrap().status(); 382 let i2 = internal.clone().oneshot(request()).await.unwrap().status(); 383 let i3 = internal.clone().oneshot(request()).await.unwrap().status(); 384 let i4 = internal.clone().oneshot(request()).await.unwrap().status(); 385 assert_eq!( 386 [i1, i2, i3, i4], 387 [StatusCode::OK; 4], 388 "the internal admin bind is unguarded, so a public flood never sheds admin requests" 389 ); 390 } 391}