diff --git a/Cargo.lock b/Cargo.lock index 2c1d3ca..17ef55f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -180,6 +180,45 @@ dependencies = [ "nom", ] +[[package]] +name = "asn1-rs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.12", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + [[package]] name = "async-channel" version = "2.5.0" @@ -1201,6 +1240,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "deranged" version = "0.4.0" @@ -1906,9 +1959,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", ] [[package]] @@ -2868,6 +2923,12 @@ dependencies = [ "hashbrown 0.15.2", ] +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "lsm-tree" version = "2.8.0" @@ -3405,6 +3466,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -3710,7 +3780,9 @@ version = "3.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f977080932c87287147dca052951c3e2696f8759863f6b4e4c0c9ffe7a4cc8b" dependencies = [ + "base64 0.22.1", "bytes", + "chrono", "futures-util", "headers", "http", @@ -3725,8 +3797,12 @@ dependencies = [ "pin-project-lite", "poem-derive", "quick-xml", + "rcgen", "regex", + "reqwest", "rfc7239", + "ring", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", @@ -3736,10 +3812,12 @@ dependencies = [ "tempfile", "thiserror 2.0.12", "tokio", + "tokio-rustls 0.26.2", "tokio-stream", "tokio-util", "tracing", "wildmatch", + "x509-parser", ] [[package]] @@ -3922,6 +4000,61 @@ dependencies = [ "hashbrown 0.15.2", ] +[[package]] +name = "quinn" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.1", + "rustls 0.23.28", + "socket2 0.5.9", + "thiserror 2.0.12", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +dependencies = [ + "bytes", + "getrandom 0.3.3", + "lru-slab", + "rand 0.9.1", + "ring", + "rustc-hash 2.1.1", + "rustls 0.23.28", + "rustls-pki-types", + "slab", + "thiserror 2.0.12", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.5.9", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "quote" version = "1.0.40" @@ -4034,6 +4167,18 @@ dependencies = [ "bitflags", ] +[[package]] +name = "rcgen" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48406db8ac1f3cbc7dcdb56ec355343817958a356ff430259bb07baf7607e1e1" +dependencies = [ + "pem", + "ring", + "time", + "yasna", +] + [[package]] name = "redox_syscall" version = "0.5.11" @@ -4144,6 +4289,9 @@ dependencies = [ "native-tls", "percent-encoding", "pin-project-lite", + "quinn", + "rustls 0.23.28", + "rustls-native-certs", "rustls-pki-types", "serde", "serde_json", @@ -4151,6 +4299,7 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-native-tls", + "tokio-rustls 0.26.2", "tokio-util", "tower", "tower-http", @@ -4266,6 +4415,15 @@ dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustix" version = "0.38.44" @@ -4313,7 +4471,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" dependencies = [ "aws-lc-rs", + "log", "once_cell", + "ring", "rustls-pki-types", "rustls-webpki 0.103.3", "subtle", @@ -4346,6 +4506,9 @@ name = "rustls-pki-types" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +dependencies = [ + "web-time", +] [[package]] name = "rustls-webpki" @@ -6245,12 +6408,38 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "x509-parser" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror 2.0.12", + "time", +] + [[package]] name = "xxhash-rust" version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + [[package]] name = "yoke" version = "0.7.5" diff --git a/slingshot/Cargo.toml b/slingshot/Cargo.toml index f69de9d..ca24da4 100644 --- a/slingshot/Cargo.toml +++ b/slingshot/Cargo.toml @@ -17,7 +17,7 @@ jetstream = { path = "../jetstream", features = ["metrics"] } log = "0.4.27" metrics = "0.24.2" metrics-exporter-prometheus = { version = "0.17.1", features = ["http-listener"] } -poem = "3.1.12" +poem = { version = "3.1.12", features = ["acme"] } poem-openapi = { version = "5.1.16", features = ["scalar"] } reqwest = { version = "0.12.22", features = ["json"] } serde = { version = "1.0.219", features = ["derive"] } diff --git a/slingshot/src/error.rs b/slingshot/src/error.rs index b2888a7..a062bfb 100644 --- a/slingshot/src/error.rs +++ b/slingshot/src/error.rs @@ -14,8 +14,10 @@ pub enum ConsumerError { #[derive(Debug, Error)] pub enum ServerError { + #[error("server build error: {0}")] + AcmeBuildError(std::io::Error), #[error("server exited: {0}")] - ServerExited(String), + ServerExited(std::io::Error), } #[derive(Debug, Error)] diff --git a/slingshot/src/server.rs b/slingshot/src/server.rs index 7e8619b..0f4b866 100644 --- a/slingshot/src/server.rs +++ b/slingshot/src/server.rs @@ -4,7 +4,14 @@ use serde::Serialize; use std::sync::Arc; use tokio_util::sync::CancellationToken; -use poem::{Endpoint, Route, Server, endpoint::make_sync, listener::TcpListener}; +use poem::{ + Endpoint, Route, Server, + endpoint::make_sync, + listener::{ + Listener, TcpListener, + acme::{AutoCert, LETS_ENCRYPT_PRODUCTION}, + }, +}; use poem_openapi::{ ApiResponse, Object, OpenApi, OpenApiService, param::Query, payload::Json, types::Example, }; @@ -211,7 +218,7 @@ struct AppViewDoc { /// /// - PDS proxying offers a level of client IP anonymity from slingshot /// - slingshot *may* implement more generous per-user rate-limits for proxied requests in the future -fn get_did_doc(host: String) -> impl Endpoint { +fn get_did_doc(host: &str) -> impl Endpoint + use<> { let doc = poem::web::Json(AppViewDoc { id: format!("did:web:{host}"), service: [AppViewService { @@ -235,17 +242,30 @@ pub async fn serve( .server("http://localhost:3000") .url_prefix("/xrpc"); - let mut app = Route::new() + let app = Route::new() .nest("/", api_service.scalar()) .nest("/openapi.json", api_service.spec_endpoint()) .nest("/xrpc/", api_service); if let Some(host) = host { - app = app.at("/.well-known/did.json", get_did_doc(host)); - }; + let app = app.at("/.well-known/did.json", get_did_doc(&host)); + + let auto_cert = AutoCert::builder() + .directory_url(LETS_ENCRYPT_PRODUCTION) + .domain(&host) + .build() + .map_err(ServerError::AcmeBuildError)?; - Server::new(TcpListener::bind("127.0.0.1:3000")) - .run(app) - .await - .map_err(|e| ServerError::ServerExited(format!("uh oh: {e:?}"))) + Server::new(TcpListener::bind("0.0.0.0:443").acme(auto_cert)) + .name("slingshot") + .run(app) + .await + .map_err(ServerError::ServerExited) + } else { + Server::new(TcpListener::bind("127.0.0.1:3000")) + .name("slingshot") + .run(app) + .await + .map_err(ServerError::ServerExited) + } }