crates/gunnar-server/tests/sso_egress_private.rs
//! **An account's own IdP CA and the operator's internal networks, at the
//! egress** (owner 2026-10-06: OIDC to a company IdP behind its own CA, on a
//! private gunnar whose IdP lives on the company's network).
//!
//! * The CA an account admin pasted is trusted for THAT call alone, beside
//! the public roots: without it — or with another CA — the IdP's
//! certificate is refused, with it the IdP answers.
//! * A network the operator listed admits the addresses inside it, live,
//! through the same pinned resolver; nothing beside it moves.
//!
//! No real DNS (a test resolver hook) and no real CA (rcgen, minted here).
#![cfg(feature = "sso-egress")] // A32-18: the airgap default carries no HTTP client; the appliance rows and CI name `sso-egress`.
use std::io::{BufRead, BufReader, Read, Write};
use std::net::{SocketAddr, TcpListener};
use std::sync::{Arc, RwLock};
use gunnar_grpc::sso::{Egress as _, PrivateNets};
use gunnar_server::sso_egress::{GuardedResolver, Lookup, UreqEgress};
fn hook(ip: &str) -> Lookup {
let ip: std::net::IpAddr = ip.parse().expect("an address");
Arc::new(move |_host: &str, port: u16| Ok(vec![SocketAddr::new(ip, port)]))
}
/// A CA, and a server certificate for `name` issued under it: (CA DER, leaf DER, leaf key DER).
fn issued(name: &str) -> (Vec<u8>, Vec<u8>, Vec<u8>) {
let ca_key = rcgen::KeyPair::generate().expect("a key");
let mut ca = rcgen::CertificateParams::new(Vec::<String>::new()).expect("params");
ca.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
ca.key_usages = vec![
rcgen::KeyUsagePurpose::KeyCertSign,
rcgen::KeyUsagePurpose::CrlSign,
];
ca.distinguished_name
.push(rcgen::DnType::CommonName, "a company's own CA");
let ca_cert = ca.self_signed(&ca_key).expect("the CA");
let issuer = rcgen::Issuer::new(ca, ca_key);
let leaf_key = rcgen::KeyPair::generate().expect("a key");
let leaf = rcgen::CertificateParams::new(vec![name.to_owned()]).expect("params");
let leaf_cert = leaf.signed_by(&leaf_key, &issuer).expect("the leaf");
(
ca_cert.der().to_vec(),
leaf_cert.der().to_vec(),
leaf_key.serialize_der(),
)
}
/// An https IdP on loopback presenting `leaf`: every request is answered
/// `200 ok`; a handshake the client refuses is dropped.
fn https_idp(leaf: Vec<u8>, key: Vec<u8>) -> u16 {
let provider = Arc::new(rustls::crypto::ring::default_provider());
let config = rustls::ServerConfig::builder_with_provider(provider)
.with_safe_default_protocol_versions()
.expect("versions")
.with_no_client_auth()
.with_single_cert(
vec![leaf.into()],
rustls::pki_types::PrivateKeyDer::Pkcs8(key.into()),
)
.expect("the server certificate");
let config = Arc::new(config);
let listener = TcpListener::bind("127.0.0.1:0").expect("bind");
let port = listener.local_addr().expect("an address").port();
std::thread::spawn(move || {
for stream in listener.incoming() {
let Ok(stream) = stream else { continue };
let config = Arc::clone(&config);
std::thread::spawn(move || {
let Ok(conn) = rustls::ServerConnection::new(config) else {
return;
};
let mut tls = rustls::StreamOwned::new(conn, stream);
let mut reader = BufReader::new(&mut tls);
let mut length = 0usize;
loop {
let mut line = String::new();
if reader.read_line(&mut line).map_or(true, |n| n == 0) {
return;
}
if let Some(v) = line.to_ascii_lowercase().strip_prefix("content-length:") {
length = v.trim().parse().unwrap_or(0);
}
if line == "\r\n" {
break;
}
}
let mut body = vec![0u8; length];
let _ = reader.read_exact(&mut body);
let _ = tls.write_all(b"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 2\r\nConnection: close\r\n\r\nok");
let _ = tls.flush();
});
}
});
port
}
/// ★ **The account's CA, and only for its call**: the public roots alone do
/// not trust a company's own CA; another company's CA does not either; the
/// CA the account admin pasted does — for discovery and for the token POST.
#[test]
fn an_accounts_own_ca_is_trusted_for_its_idp_call_and_no_other() {
let name = "idp.ca-test.example";
let (ca, leaf, key) = issued(name);
let (other_ca, _, _) = issued(name);
let port = https_idp(leaf, key);
// The test door admits this box's loopback address; https is still https.
let egress = UreqEgress::with_lookup(true, hook("127.0.0.1"));
let url = format!("https://{name}:{port}/realms/acme/.well-known/openid-configuration");
let public_only = egress
.get(&url)
.expect_err("the public roots do not know a company's CA");
assert!(
!public_only.contains("resolves to"),
"refused by TLS, not the guard: {public_only}"
);
egress
.get_trusting(&url, &[other_ca])
.expect_err("another company's CA is not this IdP's");
let (status, body) = egress
.get_trusting(&url, std::slice::from_ref(&ca))
.expect("the account's CA");
assert_eq!((status, body.as_str()), (200, "ok"));
let (status, _) = egress
.post_form_trusting(
&format!("https://{name}:{port}/realms/acme/token"),
"",
"grant_type=authorization_code",
&[ca],
)
.expect("the token endpoint, with the account's CA");
assert_eq!(status, 200);
egress
.get(&url)
.expect_err("the CA was for that call alone");
}
/// ★ **A network the operator listed admits its addresses — live — and
/// nothing beside it**; an empty list is today's guard.
#[test]
fn a_listed_internal_network_admits_its_addresses_and_nothing_beside() {
let nets = Arc::new(RwLock::new(PrivateNets::default()));
let resolver = GuardedResolver::with_nets(hook("10.20.30.40"), false, Arc::clone(&nets));
let err = resolver
.resolve_checked("idp.corp.example", 443)
.expect_err("no list: refused");
assert!(err.contains("10.20.30.40"), "{err}");
*nets.write().unwrap() = PrivateNets::parse("10.20.30.40/32").expect("a private network");
let addrs = resolver
.resolve_checked("idp.corp.example", 443)
.expect("listed, live");
assert_eq!(
addrs,
vec!["10.20.30.40:443".parse::<SocketAddr>().unwrap()],
"connected to exactly what was checked"
);
let beside = GuardedResolver::with_nets(hook("10.20.30.41"), false, Arc::clone(&nets));
assert!(
beside.resolve_checked("idp.corp.example", 443).is_err(),
"the next address is not listed"
);
let metadata = GuardedResolver::with_nets(hook("169.254.169.254"), false, Arc::clone(&nets));
assert!(metadata.resolve_checked("idp.corp.example", 443).is_err());
// A literal in the URL is judged by the same list.
let egress = UreqEgress::with_nets(false, hook("10.20.30.41"), Arc::clone(&nets));
let literal = egress
.get("https://10.20.30.41/realms/acme/.well-known/openid-configuration")
.expect_err("not listed");
assert!(literal.contains("private"), "{literal}");
}