test(server): replace generated mTLS fixtures

This commit is contained in:
Bryan Helmkamp 2026-04-05 12:24:09 -04:00
parent b63c4e664d
commit 5de18f9ca5
No known key found for this signature in database
10 changed files with 80 additions and 180 deletions

View file

@ -0,0 +1,9 @@
-----BEGIN CERTIFICATE-----
MIIBRjCB+aADAgECAhRKD83+hLEUl2GQUrsSgNaDBjPrpDAFBgMrZXAwETEPMA0G
A1UEAwwGVGVzdENBMB4XDTI2MDQwNTE2MjEzM1oXDTM2MDQwMjE2MjEzM1owETEP
MA0GA1UEAwwGVGVzdENBMCowBQYDK2VwAyEA3vVnIRyxAa9q+qtf0OPWoOUKff1D
Pq5LpXPUTh1nrJejYzBhMB0GA1UdDgQWBBT88UyTLCWai4vJtkS5K0zutivZOTAf
BgNVHSMEGDAWgBT88UyTLCWai4vJtkS5K0zutivZOTAPBgNVHRMBAf8EBTADAQH/
MA4GA1UdDwEB/wQEAwIBBjAFBgMrZXADQQBUmXc96ILueacLnf7kSJS35wiCl044
Js8vwgQuTkJ9SDhuCOt88E4b9vZMhx2kOBLiwTyTdOILhVECPE9FZicD
-----END CERTIFICATE-----

View file

@ -0,0 +1,9 @@
-----BEGIN CERTIFICATE-----
MIIBMjCB5aADAgECAhRjMLlP+97gUZFyv5k1WdriOASrLDAFBgMrZXAwETEPMA0G
A1UEAwwGVGVzdENBMB4XDTI2MDQwNTE2MjEzM1oXDTM2MDQwMjE2MjEzM1owEzER
MA8GA1UEAwwIdGVzdHVzZXIwKjAFBgMrZXADIQCYYub304Ilt7lkzkN5plpIlGCo
xR8wL18Xob7/hW2SWqNNMEswCQYDVR0TBAIwADAdBgNVHQ4EFgQUL4ve1GH0sFJ+
33TwQP1R6oactHYwHwYDVR0jBBgwFoAU/PFMkywlmouLybZEuStM7rYr2TkwBQYD
K2VwA0EAEpBsV5kpyuEF3t5GzuxELDJgtVxGLpZD5PsPqj+wxv5j6TeOwCE/LRRV
JsKYJt3SMdqySx84dfscPD9c5HMPCw==
-----END CERTIFICATE-----

View file

@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIME1COxOi67I+kdoIH+ms4c0zKA8D7M8SkeJyjC89+pj
-----END PRIVATE KEY-----

View file

@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIMr+udNo63lm79G+2xETGqoQsMJUvpbTUFhXdgKNI10C
-----END PRIVATE KEY-----

View file

@ -0,0 +1,3 @@
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA93ZZOd4zYtjwgdzSw+brqyWM9USG5INKCGWUEHRVRBw=
-----END PUBLIC KEY-----

View file

@ -0,0 +1,9 @@
-----BEGIN CERTIFICATE-----
MIIBOTCB7KADAgECAhRjMLlP+97gUZFyv5k1WdriOASrKzAFBgMrZXAwETEPMA0G
A1UEAwwGVGVzdENBMB4XDTI2MDQwNTE2MjEzM1oXDTM2MDQwMjE2MjEzM1owFDES
MBAGA1UEAwwJbG9jYWxob3N0MCowBQYDK2VwAyEAn8X6FEFjCq5MKfiSVNKjRY5p
TKdDrASo29olFWz8qy+jUzBRMA8GA1UdEQQIMAaHBH8AAAEwHQYDVR0OBBYEFL7n
tv01dMhzLJ0dzTo7tEAXrYjuMB8GA1UdIwQYMBaAFPzxTJMsJZqLi8m2RLkrTO62
K9k5MAUGAytlcANBAGpMB98RKLprVHiagV1Myj08TK2Lz4+K+Hs2fhUVMgRP9JXV
vf8tC77XV/fH9wIKeaPvsupFO73AdD0BQZb8bQ0=
-----END CERTIFICATE-----

View file

@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIAX0EXHZDH5uU2h5ctNqHfa9hMtO9tfoM1kCRL9JHGtA
-----END PRIVATE KEY-----

View file

@ -0,0 +1,9 @@
-----BEGIN CERTIFICATE-----
MIIBMzCB5qADAgECAhRT8XV12gL48jHEcgn7qTk7b5ocbzAFBgMrZXAwEjEQMA4G
A1UEAwwHV3JvbmdDQTAeFw0yNjA0MDUxNjIxMzNaFw0zNjA0MDIxNjIxMzNaMBMx
ETAPBgNVBAMMCGludHJ1ZGVyMCowBQYDK2VwAyEADrp6dr+UfNhzR6guiNU5ns0c
Y97Ari4gVZnh8DE1MB6jTTBLMAkGA1UdEwQCMAAwHQYDVR0OBBYEFIF2t8T34ktN
Y276k4702JltR0iEMB8GA1UdIwQYMBaAFNFUQIdydtMb4t9g8+0s9DHh0pYIMAUG
AytlcANBAFO7sA+Po2qFaTRSdpxuAQIbywHiF92uyombcfQkQPgbVbAA3oH9gh32
4uG4c1OCE+w1AI1f2/EpC4zZRPZVAwI=
-----END CERTIFICATE-----

View file

@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIAU8EOnIZ26wKCqJ/WTcoCBHETbSYsILQ9zxddB92JVQ
-----END PRIVATE KEY-----

View file

@ -12,8 +12,7 @@ fn api(path: &str) -> String {
#[cfg(target_os = "linux")]
mod mtls_e2e {
use super::api;
use std::path::Path;
use std::process::{Command, Stdio};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use fabro_server::jwt_auth::{AuthMode, AuthStrategy};
@ -22,152 +21,28 @@ mod mtls_e2e {
use fabro_server::tls::{ClientAuth, build_rustls_config};
use tokio::net::TcpListener;
/// Generate a complete CA + server cert + client cert PKI in `dir`.
/// Returns paths: (ca_cert, server_cert, server_key, client_cert_pem, client_key_pem)
fn generate_pki(dir: &Path, ca_cn: &str, server_cn: &str, client_cn: &str) -> PkiPaths {
// CA key
let ca_key_path = dir.join("ca.key");
let ca_cert_path = dir.join("ca.crt");
run_openssl(&[
"genpkey",
"-algorithm",
"Ed25519",
"-out",
ca_key_path.to_str().unwrap(),
]);
run_openssl(&[
"req",
"-new",
"-x509",
"-key",
ca_key_path.to_str().unwrap(),
"-out",
ca_cert_path.to_str().unwrap(),
"-days",
"1",
"-subj",
&format!("/CN={ca_cn}"),
"-addext",
"basicConstraints=critical,CA:TRUE",
"-addext",
"keyUsage=critical,keyCertSign,cRLSign",
]);
// Server key + cert signed by CA
let server_key_path = dir.join("server.key");
let server_csr_path = dir.join("server.csr");
let server_cert_path = dir.join("server.crt");
run_openssl(&[
"genpkey",
"-algorithm",
"Ed25519",
"-out",
server_key_path.to_str().unwrap(),
]);
run_openssl(&[
"req",
"-new",
"-key",
server_key_path.to_str().unwrap(),
"-out",
server_csr_path.to_str().unwrap(),
"-subj",
&format!("/CN={server_cn}"),
]);
// Create extension file for SAN (reqwest validates server cert hostname)
let ext_path = dir.join("server.ext");
std::fs::write(&ext_path, "subjectAltName=IP:127.0.0.1").unwrap();
run_openssl(&[
"x509",
"-req",
"-in",
server_csr_path.to_str().unwrap(),
"-CA",
ca_cert_path.to_str().unwrap(),
"-CAkey",
ca_key_path.to_str().unwrap(),
"-CAcreateserial",
"-out",
server_cert_path.to_str().unwrap(),
"-days",
"1",
"-extfile",
ext_path.to_str().unwrap(),
]);
// Client key + cert signed by CA
let client_key_path = dir.join("client.key");
let client_csr_path = dir.join("client.csr");
let client_cert_path = dir.join("client.crt");
run_openssl(&[
"genpkey",
"-algorithm",
"Ed25519",
"-out",
client_key_path.to_str().unwrap(),
]);
run_openssl(&[
"req",
"-new",
"-key",
client_key_path.to_str().unwrap(),
"-out",
client_csr_path.to_str().unwrap(),
"-subj",
&format!("/CN={client_cn}"),
]);
// Client extension file to produce a v3 certificate
let client_ext_path = dir.join("client.ext");
std::fs::write(&client_ext_path, "basicConstraints=CA:FALSE\n").unwrap();
run_openssl(&[
"x509",
"-req",
"-in",
client_csr_path.to_str().unwrap(),
"-CA",
ca_cert_path.to_str().unwrap(),
"-CAkey",
ca_key_path.to_str().unwrap(),
"-CAcreateserial",
"-out",
client_cert_path.to_str().unwrap(),
"-days",
"1",
"-extfile",
client_ext_path.to_str().unwrap(),
]);
fn fixture_path(name: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/mtls")
.join(name)
}
fn fixture_pki() -> PkiPaths {
PkiPaths {
ca_cert: ca_cert_path,
server_cert: server_cert_path,
server_key: server_key_path,
client_cert: client_cert_path,
client_key: client_key_path,
ca_cert: fixture_path("ca.crt"),
server_cert: fixture_path("server.crt"),
server_key: fixture_path("server.key"),
client_cert: fixture_path("client.crt"),
client_key: fixture_path("client.key"),
}
}
struct PkiPaths {
ca_cert: std::path::PathBuf,
server_cert: std::path::PathBuf,
server_key: std::path::PathBuf,
client_cert: std::path::PathBuf,
client_key: std::path::PathBuf,
}
fn run_openssl(args: &[&str]) {
let output = Command::new("openssl")
.args(args)
.stdin(Stdio::null())
.output()
.expect("openssl command failed to execute");
assert!(
output.status.success(),
"openssl {} failed: {}",
args[0],
String::from_utf8_lossy(&output.stderr)
);
ca_cert: PathBuf,
server_cert: PathBuf,
server_key: PathBuf,
client_cert: PathBuf,
client_key: PathBuf,
}
/// Start a TLS server on a random port, returning the bound address.
@ -203,6 +78,7 @@ mod mtls_e2e {
let mut builder = reqwest::Client::builder()
.add_root_certificate(ca_cert)
.no_proxy()
.use_rustls_tls();
if let (Some(cert_path), Some(key_path)) = (client_cert_path, client_key_path) {
@ -224,8 +100,7 @@ mod mtls_e2e {
#[tokio::test]
async fn mtls_accepts_valid_client_cert() {
install_crypto_provider();
let dir = tempfile::tempdir().unwrap();
let pki = generate_pki(dir.path(), "TestCA", "localhost", "testuser");
let pki = fixture_pki();
let tls_settings = TlsSettings {
cert: pki.server_cert.clone(),
@ -250,8 +125,7 @@ mod mtls_e2e {
#[tokio::test]
async fn mtls_only_rejects_wrong_ca_client_cert() {
install_crypto_provider();
let dir = tempfile::tempdir().unwrap();
let pki = generate_pki(dir.path(), "TestCA", "localhost", "testuser");
let pki = fixture_pki();
let tls_settings = TlsSettings {
cert: pki.server_cert.clone(),
@ -262,17 +136,11 @@ mod mtls_e2e {
let auth_mode = AuthMode::Strategies(vec![AuthStrategy::Mtls]);
let addr = start_tls_server(&tls_settings, ClientAuth::Required, auth_mode).await;
// Generate a DIFFERENT CA and client cert signed by it
let wrong_dir = dir.path().join("wrong_ca");
std::fs::create_dir_all(&wrong_dir).unwrap();
let wrong_pki = generate_pki(&wrong_dir, "WrongCA", "localhost", "intruder");
let wrong_client_cert = fixture_path("wrong-client.crt");
let wrong_client_key = fixture_path("wrong-client.key");
// Client trusts the REAL server CA, but presents a cert from the WRONG CA
let client = build_client(
&pki.ca_cert,
Some(&wrong_pki.client_cert),
Some(&wrong_pki.client_key),
);
let client = build_client(&pki.ca_cert, Some(&wrong_client_cert), Some(&wrong_client_key));
let result = client
.get(format!("https://127.0.0.1:{}{}", addr.port(), api("/runs")))
@ -291,8 +159,7 @@ mod mtls_e2e {
#[tokio::test]
async fn mtls_only_rejects_no_client_cert() {
install_crypto_provider();
let dir = tempfile::tempdir().unwrap();
let pki = generate_pki(dir.path(), "TestCA", "localhost", "testuser");
let pki = fixture_pki();
let tls_settings = TlsSettings {
cert: pki.server_cert.clone(),
@ -320,26 +187,9 @@ mod mtls_e2e {
);
}
/// Generate an Ed25519 JWT keypair. Returns (encoding_key, decoding_key).
fn generate_jwt_keypair() -> (jsonwebtoken::EncodingKey, jsonwebtoken::DecodingKey) {
let output = Command::new("openssl")
.args(["genpkey", "-algorithm", "Ed25519"])
.output()
.expect("openssl must be available for tests");
let private_pem = output.stdout;
let output = Command::new("openssl")
.args(["pkey", "-pubout"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.and_then(|mut child| {
use std::io::Write;
child.stdin.take().unwrap().write_all(&private_pem).unwrap();
child.wait_with_output()
})
.expect("openssl pkey failed");
let public_pem = output.stdout;
fn fixture_jwt_keypair() -> (jsonwebtoken::EncodingKey, jsonwebtoken::DecodingKey) {
let private_pem = std::fs::read(fixture_path("jwt-ed25519-private.pem")).unwrap();
let public_pem = std::fs::read(fixture_path("jwt-ed25519-public.pem")).unwrap();
let encoding =
jsonwebtoken::EncodingKey::from_ed_pem(&private_pem).expect("invalid private key");
@ -366,8 +216,7 @@ mod mtls_e2e {
#[tokio::test]
async fn mtls_and_jwt_accepts_valid_jwt_without_client_cert() {
install_crypto_provider();
let dir = tempfile::tempdir().unwrap();
let pki = generate_pki(dir.path(), "TestCA", "localhost", "testuser");
let pki = fixture_pki();
let tls_settings = TlsSettings {
cert: pki.server_cert.clone(),
@ -375,7 +224,7 @@ mod mtls_e2e {
ca: pki.ca_cert.clone(),
};
let (encoding_key, decoding_key) = generate_jwt_keypair();
let (encoding_key, decoding_key) = fixture_jwt_keypair();
// Both mTLS and JWT strategies; mTLS is optional since JWT is also present
let auth_mode = AuthMode::Strategies(vec![