a (hacky, wip) multi-tenant oidc-terminating reverse proxy, written in anger on top of pingora

add options for selfsigned backends

this adds options to either
a) skip verifying certs on self-signed backends, or
b) supply a custom ca

whoof pingora's rusttls typing is... iffy. had to do another patch
there.

+82 -29
+14 -14
Cargo.lock
··· 2488 2488 [[package]] 2489 2489 name = "pingora" 2490 2490 version = "0.7.0" 2491 - source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#9214e63b2d94b345dd5702a57b417ed6fb581996" 2491 + source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#771c7926b9635f716ddb30a24ba1d941a77596aa" 2492 2492 dependencies = [ 2493 2493 "pingora-cache", 2494 2494 "pingora-core", ··· 2501 2501 [[package]] 2502 2502 name = "pingora-cache" 2503 2503 version = "0.7.0" 2504 - source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#9214e63b2d94b345dd5702a57b417ed6fb581996" 2504 + source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#771c7926b9635f716ddb30a24ba1d941a77596aa" 2505 2505 dependencies = [ 2506 2506 "ahash", 2507 2507 "async-trait", ··· 2537 2537 [[package]] 2538 2538 name = "pingora-core" 2539 2539 version = "0.7.0" 2540 - source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#9214e63b2d94b345dd5702a57b417ed6fb581996" 2540 + source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#771c7926b9635f716ddb30a24ba1d941a77596aa" 2541 2541 dependencies = [ 2542 2542 "ahash", 2543 2543 "async-trait", ··· 2589 2589 [[package]] 2590 2590 name = "pingora-error" 2591 2591 version = "0.7.0" 2592 - source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#9214e63b2d94b345dd5702a57b417ed6fb581996" 2592 + source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#771c7926b9635f716ddb30a24ba1d941a77596aa" 2593 2593 2594 2594 [[package]] 2595 2595 name = "pingora-header-serde" 2596 2596 version = "0.7.0" 2597 - source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#9214e63b2d94b345dd5702a57b417ed6fb581996" 2597 + source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#771c7926b9635f716ddb30a24ba1d941a77596aa" 2598 2598 dependencies = [ 2599 2599 "bytes", 2600 2600 "http", ··· 2609 2609 [[package]] 2610 2610 name = "pingora-http" 2611 2611 version = "0.7.0" 2612 - source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#9214e63b2d94b345dd5702a57b417ed6fb581996" 2612 + source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#771c7926b9635f716ddb30a24ba1d941a77596aa" 2613 2613 dependencies = [ 2614 2614 "bytes", 2615 2615 "http", ··· 2619 2619 [[package]] 2620 2620 name = "pingora-ketama" 2621 2621 version = "0.7.0" 2622 - source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#9214e63b2d94b345dd5702a57b417ed6fb581996" 2622 + source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#771c7926b9635f716ddb30a24ba1d941a77596aa" 2623 2623 dependencies = [ 2624 2624 "crc32fast", 2625 2625 ] ··· 2627 2627 [[package]] 2628 2628 name = "pingora-load-balancing" 2629 2629 version = "0.7.0" 2630 - source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#9214e63b2d94b345dd5702a57b417ed6fb581996" 2630 + source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#771c7926b9635f716ddb30a24ba1d941a77596aa" 2631 2631 dependencies = [ 2632 2632 "arc-swap", 2633 2633 "async-trait", ··· 2648 2648 [[package]] 2649 2649 name = "pingora-lru" 2650 2650 version = "0.7.0" 2651 - source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#9214e63b2d94b345dd5702a57b417ed6fb581996" 2651 + source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#771c7926b9635f716ddb30a24ba1d941a77596aa" 2652 2652 dependencies = [ 2653 2653 "arrayvec", 2654 2654 "hashbrown 0.16.1", ··· 2659 2659 [[package]] 2660 2660 name = "pingora-pool" 2661 2661 version = "0.7.0" 2662 - source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#9214e63b2d94b345dd5702a57b417ed6fb581996" 2662 + source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#771c7926b9635f716ddb30a24ba1d941a77596aa" 2663 2663 dependencies = [ 2664 2664 "crossbeam-queue", 2665 2665 "log", ··· 2673 2673 [[package]] 2674 2674 name = "pingora-proxy" 2675 2675 version = "0.7.0" 2676 - source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#9214e63b2d94b345dd5702a57b417ed6fb581996" 2676 + source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#771c7926b9635f716ddb30a24ba1d941a77596aa" 2677 2677 dependencies = [ 2678 2678 "async-trait", 2679 2679 "bytes", ··· 2695 2695 [[package]] 2696 2696 name = "pingora-runtime" 2697 2697 version = "0.7.0" 2698 - source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#9214e63b2d94b345dd5702a57b417ed6fb581996" 2698 + source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#771c7926b9635f716ddb30a24ba1d941a77596aa" 2699 2699 dependencies = [ 2700 2700 "once_cell", 2701 2701 "rand 0.8.5", ··· 2706 2706 [[package]] 2707 2707 name = "pingora-rustls" 2708 2708 version = "0.7.0" 2709 - source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#9214e63b2d94b345dd5702a57b417ed6fb581996" 2709 + source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#771c7926b9635f716ddb30a24ba1d941a77596aa" 2710 2710 dependencies = [ 2711 2711 "log", 2712 2712 "no_debug", ··· 2722 2722 [[package]] 2723 2723 name = "pingora-timeout" 2724 2724 version = "0.7.0" 2725 - source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#9214e63b2d94b345dd5702a57b417ed6fb581996" 2725 + source = "git+https://github.com/directxman12/pingora?branch=custom%2Fmain#771c7926b9635f716ddb30a24ba1d941a77596aa" 2726 2726 dependencies = [ 2727 2727 "once_cell", 2728 2728 "parking_lot",
+13
src/config/format.proto
··· 141 141 string addr = 1; 142 142 // weight of this backend, if load-balancing 143 143 optional uint64 weight = 2; 144 + 145 + // skip verifying certificates on tls backends 146 + // 147 + // useful if your backend certs are self-signed, 148 + // or for some reason not valid for the backend itself. 149 + // 150 + // generally prefer to use `ca_path` instead. 151 + bool skip_verifying_certs = 3; 152 + 153 + // path to the CA file used to verify the backend's certs, on tls backends 154 + // 155 + // useful if the backend is using self-signed certs 156 + optional string ca_path = 4; 144 157 } 145 158 146 159 // a unix domain socket backend
+34 -11
src/gateway.rs
··· 10 10 use http::{HeaderName, HeaderValue}; 11 11 use pingora::lb::selection::consistent::KetamaHashing; 12 12 use pingora::prelude::*; 13 + use pingora::protocols::tls::CaType; 13 14 use url::Url; 14 15 15 16 use crate::gateway::oidc::{InProgressAuth, SESSION_COOKIE_NAME, UserInfo}; ··· 618 619 StatusCode::SERVICE_UNAVAILABLE, 619 620 ))?; 620 621 621 - let needs_tls = backend 622 + let backend_data = backend 622 623 .ext 623 624 .get::<BackendData>() 624 - .map(|d| d.tls) 625 - .unwrap_or(true); 625 + .cloned() // just some bools and an arc, it's fine 626 + .unwrap_or_default(); 626 627 627 - Ok(Box::new(HttpPeer::new( 628 - backend, 629 - needs_tls, 630 - backends.sni_name.to_string(), 631 - ))) 628 + let peer = match backend_data { 629 + BackendData::HttpOnly => HttpPeer::new(backend, false, backends.sni_name.to_string()), 630 + BackendData::Tls { 631 + skip_verifying_cert, 632 + ca, 633 + } => { 634 + let mut peer = HttpPeer::new(backend, true, backends.sni_name.to_string()); 635 + peer.options.verify_cert = !skip_verifying_cert; 636 + peer.options.ca = ca; 637 + peer 638 + } 639 + }; 640 + Ok(Box::new(peer)) 632 641 } 633 642 634 643 async fn response_filter( ··· 663 672 /// 664 673 /// for use in [`AuthGateway::upstream_peer`] 665 674 #[derive(Clone)] 666 - pub struct BackendData { 667 - /// does the backend want tls 668 - pub tls: bool, 675 + pub enum BackendData { 676 + HttpOnly, 677 + Tls { 678 + /// should skip we verifying the cert (useful if the certs are selfsigned and we don't want to 679 + /// have them loaded) 680 + skip_verifying_cert: bool, 681 + /// custom ca to use to verify backend certs 682 + ca: Option<Arc<CaType>>, 683 + }, 684 + } 685 + impl Default for BackendData { 686 + fn default() -> Self { 687 + Self::Tls { 688 + skip_verifying_cert: false, 689 + ca: None, 690 + } 691 + } 669 692 }
+21 -4
src/main.rs
··· 5 5 use pingora::lb::selection::consistent::KetamaHashing; 6 6 use pingora::listeners::tls::{BundleCert, CertAndKey, TlsSettings}; 7 7 use pingora::prelude::*; 8 + use pingora::utils::tls::CertKey; 8 9 9 10 use self::gateway::{AuthGateway, BackendData, DomainInfo, oidc}; 10 11 ··· 34 35 .iter() 35 36 .map(|backend| { 36 37 let mut ext = lb::Extensions::new(); 37 - ext.insert(BackendData { tls: true }); 38 + let ca = backend.ca_path.as_ref().map(|path| { 39 + // yikes pingora does not make this easy to construct 40 + CertKey::new( 41 + vec![std::fs::read(path).expect("unable to read ca path")], 42 + vec![], 43 + ) 44 + .into_certs() 45 + }); 46 + ext.insert(BackendData::Tls { 47 + skip_verifying_cert: backend.skip_verifying_certs, 48 + ca, 49 + }); 38 50 let mut backend = Backend::new_with_weight( 39 51 &backend.addr, 40 52 backend.weight.map(|w| w as usize).unwrap_or(1), ··· 45 57 }) 46 58 .chain(domain.http.iter().map(|backend| { 47 59 let mut ext = lb::Extensions::new(); 48 - ext.insert(BackendData { tls: false }); 60 + ext.insert(BackendData::HttpOnly); 49 61 let mut backend = Backend::new_with_weight( 50 62 &backend.addr, 51 63 backend.weight.map(|w| w as usize).unwrap_or(1), ··· 56 68 })) 57 69 .chain(domain.uds.iter().map(|backend| { 58 70 let mut ext = lb::Extensions::new(); 59 - ext.insert(BackendData { tls: false }); 71 + ext.insert(BackendData::HttpOnly); 60 72 Ok(Backend { 61 73 addr: SocketAddr::Unix( 62 74 std::os::unix::net::SocketAddr::from_pathname(&backend.path) ··· 76 88 balancer: svc.task(), 77 89 tls_mode: config::format::domain::TlsMode::try_from(domain.tls_mode) 78 90 .context("invalid tls mode")?, 79 - sni_name: name.clone(), 91 + sni_name: domain 92 + .tls 93 + .as_ref() 94 + .and_then(|d| d.sni.as_ref()) 95 + .unwrap_or(name) 96 + .clone(), 80 97 oidc: domain 81 98 .oidc_auth 82 99 .clone()