Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .add/dependencies.allowlist
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ ctrlc
metrics
metrics-exporter-prometheus
rustls
rustls-pemfile
rustls-pki-types
aws-lc-rs
tokio-rustls
monoio-rustls
Expand Down
6 changes: 1 addition & 5 deletions .cargo/audit.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# unsound / yanked print but do not fail) — deliberately NOT overridden
# with `--deny warnings` here, matching the design that a real CVE hard
# fails while informational advisories stay visible-but-non-blocking. The
# entries below are additionally silenced via `ignore` (same three
# entries below are additionally silenced via `ignore` (same two
# unmaintained crates as deny.toml, plus one unsound-but-inapplicable
# advisory not caught by cargo-deny's older rustsec matcher) purely to
# keep CI logs clean; each has a reason and a re-check trigger.
Expand All @@ -21,10 +21,6 @@ ignore = [
# pinned transitively by tikv-jemalloc-ctl 0.6.1. Revisit on next
# jemalloc-ctl major bump.
"RUSTSEC-2024-0436",
# rustls-pemfile 2.2.0: unmaintained; migrating to
# rustls-pki-types::PemObject is a source-code change (src/tls*) out
# of scope for this CI-wiring change. Tracked as a follow-up.
"RUSTSEC-2025-0134",
# rand 0.9.2 (transitive via metrics-util 0.20.4 -> metrics -> moon):
# RUSTSEC-2026-0097 is a narrow unsoundness that only triggers when a
# *custom log::Log implementation* calls rand::rng() reentrantly from
Expand Down
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ updates:
async-runtime:
patterns: ["tokio*", "monoio*", "futures*"]
crypto-tls:
patterns: ["rustls*", "aws-lc-rs*", "rustls-pemfile*"]
patterns: ["rustls*", "aws-lc-rs*"]
tracing:
patterns: ["tracing*"]
ignore:
Expand Down
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
10×-RAM benchmark publication), cluster hardening + multi-shard replicas moved
to v0.9, enterprise foundation to v0.10; debt register refreshed.

### Changed
- **TLS: migrated off the unmaintained `rustls-pemfile` onto `rustls-pki-types`'s
`PemObject` trait (task #66).** `build_tls_config` (`src/tls.rs`) now parses
certificate chains via `CertificateDer::pem_reader_iter` and private keys via
`PrivateKeyDer::from_pem_reader` — both provided directly by `rustls-pki-types`
(already in the dependency graph as rustls's own `pki_types` re-export), so no
new supply-chain surface is added. Behavior is unchanged: same fail-loud
`io::Error` wrapping per stage (`TLS cert file` / `TLS cert parse` / `TLS key
file` / `TLS key parse` / `CA cert parse`), same SIGHUP hot-reload path. Two
new unit tests (`test_build_tls_config_garbage_cert_parse_error`,
`test_build_tls_config_garbage_key_parse_error`) exercise the parser seam
directly with corrupt-but-well-formed PEM bodies — the existing suite only
covered missing-file paths, not actual DER decode failures. `rustls-pemfile`
is fully removed from `Cargo.toml`/`Cargo.lock`; the RUSTSEC-2025-0134
ignore entries in `deny.toml` and `.cargo/audit.toml` are removed since the
advisory no longer applies. `cargo deny check advisories licenses bans
sources` and `cargo audit` both pass clean with no ignore needed.

## [0.7.1] — 2026-07-15

Patch release closing the two follow-ups disclosed in the v0.7.0 tag notes: the
Expand Down
11 changes: 1 addition & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ ctrlc = "3.4"
metrics = "0.24"
metrics-exporter-prometheus = { version = "0.18", default-features = false, features = ["http-listener"] }
rustls = { version = "0.23", default-features = false, features = ["std", "tls12"], optional = true }
rustls-pemfile = { version = "2", optional = true }
rustls-pki-types = { version = "1", optional = true, features = ["std"] }
aws-lc-rs = { version = "1", optional = true }
tokio-rustls = { version = "0.26", optional = true }
monoio-rustls = { version = "0.4", optional = true }
Expand Down Expand Up @@ -103,8 +103,8 @@ jemalloc = ["dep:tikv-jemallocator", "dep:tikv-jemalloc-ctl"]
# Usage:
# cargo build --no-default-features --features runtime-monoio,mimalloc-alt,graph,text-index
mimalloc-alt = []
runtime-tokio = ["tokio/rt-multi-thread", "tokio/io-util", "tokio/signal", "tokio/time", "tokio/fs", "tokio/process", "dep:tokio-util", "dep:tokio-rustls", "dep:aws-lc-rs", "dep:rustls", "rustls/aws_lc_rs", "dep:rustls-pemfile"]
runtime-monoio = ["dep:monoio", "dep:monoio-rustls", "dep:aws-lc-rs", "dep:rustls", "rustls/aws_lc_rs", "dep:rustls-pemfile"]
runtime-tokio = ["tokio/rt-multi-thread", "tokio/io-util", "tokio/signal", "tokio/time", "tokio/fs", "tokio/process", "dep:tokio-util", "dep:tokio-rustls", "dep:aws-lc-rs", "dep:rustls", "rustls/aws_lc_rs", "dep:rustls-pki-types"]
runtime-monoio = ["dep:monoio", "dep:monoio-rustls", "dep:aws-lc-rs", "dep:rustls", "rustls/aws_lc_rs", "dep:rustls-pki-types"]
gpu-cuda = ["dep:cudarc"]
simd-avx512 = []
# OpenTelemetry exporter: reserves feature namespace for OTLP trace export.
Expand Down
6 changes: 0 additions & 6 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,6 @@ ignore = [
# archived by upstream, no safe upgrade (the suggested forks — pastey /
# with_builtin_macros — are not drop-in without a tikv-jemalloc-ctl bump).
{ id = "RUSTSEC-2024-0436", reason = "paste is unmaintained (archived upstream) with no safe upgrade; pinned transitively by tikv-jemalloc-ctl 0.6.1 — revisit on next jemalloc-ctl major bump" },
# rustls-pemfile 2.2.0 (direct, optional dep behind runtime-tokio /
# runtime-monoio for TLS PEM loading): unmaintained, no safe drop-in
# upgrade in the 2.x line. Migrating to rustls-pki-types' PemObject API
# is a real code change (src/tls*), tracked separately — not done here
# to keep this CI-wiring change infra-only.
{ id = "RUSTSEC-2025-0134", reason = "rustls-pemfile is unmaintained; migrating callers to rustls-pki-types::PemObject is a source-code change out of scope for CI wiring — tracked as a follow-up" },
]

[licenses]
Expand Down
126 changes: 103 additions & 23 deletions src/tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use std::io;
use std::sync::Arc;

use arc_swap::ArcSwap;
use rustls_pki_types::pem::PemObject;
use rustls_pki_types::{CertificateDer, PrivateKeyDer};
use tracing::info;

/// Map cipher suite name strings to rustls cipher suite constants.
Expand Down Expand Up @@ -73,26 +75,17 @@ pub fn build_tls_config(
// Load certificate chain
let cert_file = std::fs::File::open(cert_path)
.map_err(|e| io::Error::new(io::ErrorKind::NotFound, format!("TLS cert file: {}", e)))?;
let mut cert_reader = io::BufReader::new(cert_file);
let certs: Vec<rustls::pki_types::CertificateDer<'static>> =
rustls_pemfile::certs(&mut cert_reader)
.collect::<Result<Vec<_>, _>>()
.map_err(|e| {
io::Error::new(io::ErrorKind::InvalidData, format!("TLS cert parse: {}", e))
})?;
let certs: Vec<CertificateDer<'static>> = CertificateDer::pem_reader_iter(cert_file)
.collect::<Result<Vec<_>, _>>()
.map_err(|e| {
io::Error::new(io::ErrorKind::InvalidData, format!("TLS cert parse: {}", e))
})?;

// Load private key
let key_file = std::fs::File::open(key_path)
.map_err(|e| io::Error::new(io::ErrorKind::NotFound, format!("TLS key file: {}", e)))?;
let mut key_reader = io::BufReader::new(key_file);
let key = rustls_pemfile::private_key(&mut key_reader)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("TLS key parse: {}", e)))?
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
"No private key found in TLS key file",
)
})?;
let key: PrivateKeyDer<'static> = PrivateKeyDer::from_pem_reader(key_file)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("TLS key parse: {}", e)))?;

// Explicit default cipher suite allowlist.
//
Expand Down Expand Up @@ -135,13 +128,11 @@ pub fn build_tls_config(
let ca_file = std::fs::File::open(ca_path).map_err(|e| {
io::Error::new(io::ErrorKind::NotFound, format!("TLS CA cert file: {}", e))
})?;
let mut ca_reader = io::BufReader::new(ca_file);
let ca_certs: Vec<rustls::pki_types::CertificateDer<'static>> =
rustls_pemfile::certs(&mut ca_reader)
.collect::<Result<Vec<_>, _>>()
.map_err(|e| {
io::Error::new(io::ErrorKind::InvalidData, format!("CA cert parse: {}", e))
})?;
let ca_certs: Vec<CertificateDer<'static>> = CertificateDer::pem_reader_iter(ca_file)
.collect::<Result<Vec<_>, _>>()
.map_err(|e| {
io::Error::new(io::ErrorKind::InvalidData, format!("CA cert parse: {}", e))
})?;

let mut root_store = rustls::RootCertStore::empty();
for cert in ca_certs {
Expand Down Expand Up @@ -334,6 +325,95 @@ mod tests {
assert!(result.is_err());
}

/// Exercises the rustls-pki-types `CertificateDer::pem_reader_iter` parse
/// seam directly: a file with well-formed PEM boundary markers but a
/// corrupt base64 body must surface as a "TLS cert parse" error, not a
/// panic or a silently-empty cert chain.
#[test]
fn test_build_tls_config_garbage_cert_parse_error() {
let dir = std::env::temp_dir().join("moon-tls-garbage-cert-test");
let _ = std::fs::create_dir_all(&dir);
let cert_path = dir.join("garbage.crt");
let key_path = dir.join("unused.key");
std::fs::write(
&cert_path,
"-----BEGIN CERTIFICATE-----\n!!!not valid base64!!!\n-----END CERTIFICATE-----\n",
)
.unwrap();

let result = build_tls_config(
cert_path.to_str().unwrap(),
key_path.to_str().unwrap(),
None,
None,
);
assert!(result.is_err());
assert!(
result.unwrap_err().to_string().contains("TLS cert parse"),
"corrupt PEM body must be reported as a cert parse error"
);

let _ = std::fs::remove_dir_all(&dir);
}

/// Same seam as above for the private key: `PrivateKeyDer::from_pem_reader`
/// must fail loud on a corrupt PKCS8 body rather than panicking or
/// returning a bogus key.
#[test]
fn test_build_tls_config_garbage_key_parse_error() {
let dir = std::env::temp_dir().join("moon-tls-garbage-key-test");
let _ = std::fs::create_dir_all(&dir);
let cert_path = dir.join("valid.crt");
let key_path = dir.join("garbage.key");

// Generate a real cert so we reach key parsing.
let status = std::process::Command::new("openssl")
.args([
"req",
"-x509",
"-newkey",
"ec",
"-pkeyopt",
"ec_paramgen_curve:prime256v1",
"-keyout",
dir.join("throwaway.key").to_str().unwrap(),
"-out",
cert_path.to_str().unwrap(),
"-days",
"1",
"-nodes",
"-subj",
"/CN=moon-garbage-key-test",
])
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status();
if !matches!(status, Ok(s) if s.success()) {
eprintln!("openssl not available, skipping TLS garbage key test");
let _ = std::fs::remove_dir_all(&dir);
return;
}
std::fs::write(
&key_path,
"-----BEGIN PRIVATE KEY-----\n!!!not valid base64!!!\n-----END PRIVATE KEY-----\n",
)
.unwrap();

let result = build_tls_config(
cert_path.to_str().unwrap(),
key_path.to_str().unwrap(),
None,
None,
);
assert!(result.is_err());
assert!(
result.unwrap_err().to_string().contains("TLS key parse"),
"corrupt PEM body must be reported as a key parse error"
);

let _ = std::fs::remove_dir_all(&dir);
}

#[test]
fn test_reload_tls_config_swaps_config() {
let dir = std::env::temp_dir().join("moon-tls-reload-test");
Expand Down
Loading