gunnar-server 2.0.0

The gunnar git server: SSH and smart-HTTP git transports and the gRPC control plane. The appliance half, extracted from gunnar-cli so the server code lives in its own crate.
Documentation
//! **The fake company IdP over https, under a throwaway CA it hands out** (the estate-box
//! gate's SSO1, 2026-10-12: gunnar-ui sends a person only to an https identity provider —
//! `sso_error=unsafe-provider` against the http fake). `fake_idp --https --oidc-bind <ip>`
//! serves its OpenID provider at `https://<ip>:<port>`, its certificate issued for that
//! address under a CA it prints as `oidc_ca_pem` — the CA an account admin hands gunnar as
//! the provider's `idp_ca_pem`. Discovery is read here over TLS verified against that CA
//! alone.

use std::io::{BufRead, BufReader, Read, Write};
use std::path::PathBuf;
use std::sync::Arc;

/// The example binary cargo built beside this test (`target/<profile>/examples/fake_idp`).
fn fake_idp() -> PathBuf {
    let me = std::env::current_exe().expect("this test's path");
    let profile = me.parent().and_then(|deps| deps.parent()).expect("target/<profile>");
    profile.join("examples").join("fake_idp")
}

/// The CA's DER, read as gunnar reads an account's IdP CA.
fn ca_der(pem: &str) -> Vec<u8> {
    let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis() as i64;
    gunnar_grpc::sso::idp_ca::parse_idp_ca(pem, now).expect("an IdP CA gunnar takes").remove(0)
}

/// GET `path` from `issuer` over TLS, `ca_pem` the only root: (status line, body).
fn get_https(issuer: &str, ca_pem: &str, path: &str) -> (String, String) {
    let addr = issuer.strip_prefix("https://").expect("an https issuer");
    let host = addr.rsplit_once(':').expect("host:port").0.to_owned();
    let mut roots = rustls::RootCertStore::empty();
    roots.add(rustls::pki_types::CertificateDer::from(ca_der(ca_pem))).expect("the CA");
    let cfg = rustls::ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider()))
        .with_safe_default_protocol_versions()
        .expect("versions")
        .with_root_certificates(roots)
        .with_no_client_auth();
    let name = rustls::pki_types::ServerName::try_from(host).expect("a name");
    let conn = rustls::ClientConnection::new(Arc::new(cfg), name).expect("a client");
    let sock = std::net::TcpStream::connect(addr).expect("the fake listens");
    let mut tls = rustls::StreamOwned::new(conn, sock);
    write!(tls, "GET {path} HTTP/1.1\r\nHost: {addr}\r\nConnection: close\r\n\r\n").expect("a request");
    let mut raw = Vec::new();
    let _ = tls.read_to_end(&mut raw);
    let text = String::from_utf8_lossy(&raw).to_string();
    let (head, body) = text.split_once("\r\n\r\n").expect("an HTTP answer");
    (head.lines().next().unwrap_or_default().to_owned(), body.to_owned())
}

/// The fake, killed whatever the test's end.
struct Running(std::process::Child);

impl Drop for Running {
    fn drop(&mut self) {
        let _ = self.0.kill();
        let _ = self.0.wait();
    }
}

#[test]
fn the_fake_idp_serves_its_openid_provider_over_https_and_hands_out_its_ca() {
    let child = std::process::Command::new(fake_idp())
        .args(["--client-id", "bench", "--client-secret", "s3cret", "--https", "--oidc-bind", "127.0.0.1"])
        .stdout(std::process::Stdio::piped())
        .stderr(std::process::Stdio::null())
        .spawn()
        .expect("the example runs");
    let mut child = Running(child);
    let mut line = String::new();
    let mut out = BufReader::new(child.0.stdout.take().expect("stdout"));
    out.read_line(&mut line).expect("its line");
    let up: serde_json::Value = serde_json::from_str(&line).unwrap_or_else(|e| panic!("{e}: {line:?}"));
    let issuer = up["oidc_issuer"].as_str().expect("an issuer").to_owned();
    assert!(issuer.starts_with("https://127.0.0.1:"), "{issuer}");
    let ca = up["oidc_ca_pem"].as_str().expect("the CA it is issued under").to_owned();
    assert!(ca.contains("BEGIN CERTIFICATE") && !ca.contains("PRIVATE KEY"));
    assert!(!line.contains("s3cret"), "the client secret is never printed");
    // gunnar takes it as an IdP CA (a CA, valid now).
    let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis() as i64;
    assert_eq!(gunnar_grpc::sso::idp_ca::parse_idp_ca(&ca, now).expect("an IdP CA gunnar takes").len(), 1);
    let (status, body) = get_https(&issuer, &ca, "/.well-known/openid-configuration");
    assert!(status.contains("200"), "{status}");
    let doc: serde_json::Value = serde_json::from_str(&body).expect("discovery");
    assert_eq!(doc["issuer"], issuer);
    assert!(doc["authorization_endpoint"].as_str().unwrap().starts_with("https://"));
}