jsonrpc: optional TLS termination (HTTP/2)
[jsonrpc].tls = { cert, key } makes the endpoint terminate TLS itself,
which also enables HTTP/2 (ALPN), so traffic is encrypted right up to
fedserv — for when it runs on a different host from the site, or the
browser's TLS should reach it directly. Absent = plain HTTP for a
reverse-proxy front. Reuses the same cert/key shape as [grpc]; a crypto
provider is pinned before building the config (the tree carries more than
one). Verified the TLS config builds against a real cert without a
provider panic.
This commit is contained in:
parent
92417e7a7c
commit
540d2f39d1
4 changed files with 75 additions and 7 deletions
42
Cargo.lock
generated
42
Cargo.lock
generated
|
|
@ -17,6 +17,15 @@ version = "1.0.103"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
|
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arc-swap"
|
||||||
|
version = "1.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b"
|
||||||
|
dependencies = [
|
||||||
|
"rustversion",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "argon2"
|
name = "argon2"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
|
|
@ -152,6 +161,28 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum-server"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c1ab4a3ec9ea8a657c72d99a03a824af695bd0fb5ec639ccbd9cd3543b41a5f9"
|
||||||
|
dependencies = [
|
||||||
|
"arc-swap",
|
||||||
|
"bytes",
|
||||||
|
"fs-err",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"hyper",
|
||||||
|
"hyper-util",
|
||||||
|
"pin-project-lite",
|
||||||
|
"rustls",
|
||||||
|
"rustls-pemfile",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"tokio",
|
||||||
|
"tokio-rustls",
|
||||||
|
"tower-service",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.22.1"
|
version = "0.22.1"
|
||||||
|
|
@ -292,6 +323,7 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argon2",
|
"argon2",
|
||||||
"axum",
|
"axum",
|
||||||
|
"axum-server",
|
||||||
"base64",
|
"base64",
|
||||||
"fedserv-api",
|
"fedserv-api",
|
||||||
"fedserv-botserv",
|
"fedserv-botserv",
|
||||||
|
|
@ -400,6 +432,16 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fs-err"
|
||||||
|
version = "3.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b91aa448ca50d7e79433bdf3ee8d99215430d2ec02ade5aefab2a073a1822e8a"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fs_extra"
|
name = "fs_extra"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ tokio-rustls = "0.26.4"
|
||||||
rustls-pemfile = "2.2.0"
|
rustls-pemfile = "2.2.0"
|
||||||
tonic = { version = "0.12", features = ["tls"] }
|
tonic = { version = "0.12", features = ["tls"] }
|
||||||
axum = "0.7"
|
axum = "0.7"
|
||||||
|
axum-server = { version = "0.7", features = ["tls-rustls"] }
|
||||||
prost = "0.13"
|
prost = "0.13"
|
||||||
tokio-stream = "0.1"
|
tokio-stream = "0.1"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,10 @@ pub struct JsonRpc {
|
||||||
// ["https://tchatou.fr", "https://swaygo.fr"]. Empty = no browser access.
|
// ["https://tchatou.fr", "https://swaygo.fr"]. Empty = no browser access.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub origins: Vec<String>,
|
pub origins: Vec<String>,
|
||||||
|
// Terminate TLS here (enabling HTTP/2). Absent = plain HTTP, for when a
|
||||||
|
// reverse proxy does TLS. Reuses the same cert/key shape as [grpc].
|
||||||
|
#[serde(default)]
|
||||||
|
pub tls: Option<ServerTls>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
|
|
||||||
|
|
@ -38,11 +38,30 @@ pub async fn run(engine: Shared, cfg: JsonRpcCfg) {
|
||||||
if cfg.token.is_empty() {
|
if cfg.token.is_empty() {
|
||||||
return tracing::error!("jsonrpc token is empty; refusing to start an unauthenticated stats endpoint");
|
return tracing::error!("jsonrpc token is empty; refusing to start an unauthenticated stats endpoint");
|
||||||
}
|
}
|
||||||
|
let tls = cfg.tls.clone();
|
||||||
let state = AppState { engine, token: cfg.token, origins: Arc::new(cfg.origins) };
|
let state = AppState { engine, token: cfg.token, origins: Arc::new(cfg.origins) };
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", post(handle).options(preflight))
|
.route("/", post(handle).options(preflight))
|
||||||
.layer(DefaultBodyLimit::max(64 * 1024))
|
.layer(DefaultBodyLimit::max(64 * 1024))
|
||||||
.with_state(state);
|
.with_state(state);
|
||||||
|
|
||||||
|
match tls {
|
||||||
|
// TLS terminated here → HTTP/2 (ALPN) plus HTTP/1.1, encrypted to fedserv.
|
||||||
|
Some(t) => {
|
||||||
|
// The dependency tree carries more than one rustls crypto provider, so
|
||||||
|
// pin one before building any TLS config (ignored if already set).
|
||||||
|
let _ = tokio_rustls::rustls::crypto::aws_lc_rs::default_provider().install_default();
|
||||||
|
let config = match axum_server::tls_rustls::RustlsConfig::from_pem_file(&t.cert, &t.key).await {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => return tracing::error!(%e, "jsonrpc TLS cert/key unreadable"),
|
||||||
|
};
|
||||||
|
tracing::info!(%addr, "jsonrpc stats API listening (TLS, HTTP/2)");
|
||||||
|
if let Err(e) = axum_server::bind_rustls(addr, config).serve(app.into_make_service()).await {
|
||||||
|
tracing::error!(%e, "jsonrpc server exited");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Plain HTTP, for when a reverse proxy terminates TLS/HTTP-2/HTTP-3.
|
||||||
|
None => {
|
||||||
let listener = match tokio::net::TcpListener::bind(addr).await {
|
let listener = match tokio::net::TcpListener::bind(addr).await {
|
||||||
Ok(l) => l,
|
Ok(l) => l,
|
||||||
Err(e) => return tracing::error!(%e, %addr, "jsonrpc bind failed"),
|
Err(e) => return tracing::error!(%e, %addr, "jsonrpc bind failed"),
|
||||||
|
|
@ -52,6 +71,8 @@ pub async fn run(engine: Shared, cfg: JsonRpcCfg) {
|
||||||
tracing::error!(%e, "jsonrpc server exited");
|
tracing::error!(%e, "jsonrpc server exited");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Preflight (OPTIONS): tell the browser this origin may POST with an
|
// Preflight (OPTIONS): tell the browser this origin may POST with an
|
||||||
// Authorization header. Only origins on the allowlist are answered.
|
// Authorization header. Only origins on the allowlist are answered.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue