11use crate :: {
22 Error , Result ,
3- core:: models:: proxy:: { ProxyConfig , ProxyRoute } ,
3+ core:: {
4+ handlers:: filesystem:: read_file,
5+ models:: {
6+ certs:: { TlsCerts , TlsMaterial } ,
7+ proxy:: { ProxyConfig , ProxyRoute , ProxyRouteMap } ,
8+ routes:: Host ,
9+ } ,
10+ } ,
11+ error,
412} ;
513use async_trait:: async_trait;
614use http:: { Response , header} ;
715use pingora:: {
16+ ErrorType ,
817 apps:: http_app:: ServeHttp ,
9- http:: StatusCode ,
10- prelude:: { HttpPeer , Result as PingoraResult } ,
18+ http:: { ResponseHeader , StatusCode } ,
19+ listeners:: { TlsAccept , tls:: TlsSettings } ,
20+ prelude:: { Error as PingoraError , HttpPeer , Result as PingoraResult } ,
1121 protocols:: http:: ServerSession ,
1222 proxy:: { ProxyHttp , Session , http_proxy_service} ,
1323 server:: { Server , configuration:: ServerConf } ,
1424 services:: { Service , listening:: Service as ListeningService } ,
25+ tls:: { self , ssl} ,
1526} ;
16- use std:: sync:: Arc ;
17-
18- static BODY : & [ u8 ] = "<html><body>301 Moved Permanently</body></html>" . as_bytes ( ) ;
27+ use std:: { collections:: HashMap , sync:: Arc } ;
1928
2029pub fn run_proxy ( proxy_config : ProxyConfig , routes : Vec < ProxyRoute > ) -> Result < ( ) > {
2130 let mut server = Server :: new ( None ) . map_err ( |e| Error :: Proxy ( e. to_string ( ) ) ) ?;
@@ -26,89 +35,92 @@ pub fn run_proxy(proxy_config: ProxyConfig, routes: Vec<ProxyRoute>) -> Result<(
2635 let http_addr = format ! ( "{}:{}" , proxy_config. input_address, * proxy_config. http_port) ;
2736 let https_addr = format ! ( "{}:{}" , proxy_config. input_address, * proxy_config. https_port) ;
2837
29- // tls proxies
30- let tls_services =
31- tls_routes_services ( server_conf. clone ( ) , https_addr. clone ( ) , routes. clone ( ) ) ?;
38+ let mut routes_map = HashMap :: new ( ) ;
39+ let mut tls_certs = HashMap :: new ( ) ;
40+
41+ for route in routes {
42+ if * route. tls {
43+ let cert_bytes = read_file ( & route. cert_path ) ?;
44+ let cert = tls:: x509:: X509 :: from_pem ( & cert_bytes)
45+ . map_err ( |e| Error :: Certificate ( format ! ( "Failed to parse certificate: {}" , e) ) ) ?;
46+
47+ let key_bytes = read_file ( & route. key_path ) ?;
48+ let key = tls:: pkey:: PKey :: private_key_from_pem ( & key_bytes)
49+ . map_err ( |e| Error :: Certificate ( format ! ( "Failed to parse private key: {}" , e) ) ) ?;
50+
51+ tls_certs. insert (
52+ route. host . clone ( ) ,
53+ TlsMaterial {
54+ cert,
55+ key,
56+ } ,
57+ ) ;
58+ }
3259
33- for service in tls_services {
34- server. add_service ( service) ;
60+ routes_map. insert ( route. host . clone ( ) , route) ;
3561 }
3662
37- // redirect to tls proxies
38- let redirect_service = http_redirect_service ( http_addr . clone ( ) ) ;
63+ let routes_map = Arc :: new ( routes_map ) ;
64+ let tls_certs = Arc :: new ( tls_certs ) ;
3965
40- server. add_service ( redirect_service) ;
41- // plain proxies
42- let plain_services = plain_routes_services ( server_conf, http_addr. clone ( ) , routes. clone ( ) ) ?;
66+ // plain proxies with redirect
67+ let plain_service =
68+ plain_routes_service ( server_conf. clone ( ) , http_addr. clone ( ) , routes_map. clone ( ) ) ?;
69+ server. add_service ( plain_service) ;
4370
44- for service in plain_services {
45- server. add_service ( service) ;
46- }
71+ // tls proxies
72+ let tls_service =
73+ tls_routes_service ( server_conf, https_addr. clone ( ) , routes_map. clone ( ) , tls_certs) ?;
74+ server. add_service ( tls_service) ;
4775
4876 server. run_forever ( ) ;
4977
5078 Err ( Error :: Proxy ( "proxy stopped" . to_string ( ) ) )
5179}
5280
53- pub fn tls_routes_services (
81+ pub fn tls_routes_service (
5482 server_conf : Arc < ServerConf > ,
5583 listen_addr : String ,
56- routes : Vec < ProxyRoute > ,
57- ) -> Result < Vec < impl Service > > {
58- let mut services = Vec :: new ( ) ;
59-
60- let tls_routes = routes. into_iter ( ) . filter ( |route| * route. tls == true ) . collect :: < Vec < _ > > ( ) ;
84+ routes_map : ProxyRouteMap ,
85+ tls_certs : TlsCerts ,
86+ ) -> Result < impl Service > {
87+ let proxy_app = ProxyToUpstream :: new ( routes_map. clone ( ) , false ) ;
6188
62- for route in tls_routes {
63- let proxy_app = ProxyToUpstream :: new ( route. clone ( ) ) ;
64-
65- let mut service = http_proxy_service ( & server_conf, proxy_app) ;
66- service
67- . add_tls ( & listen_addr, & route. cert_path , & route. key_path )
68- . map_err ( |e| Error :: Proxy ( e. to_string ( ) ) ) ;
89+ let mut service = http_proxy_service ( & server_conf, proxy_app) ;
90+ service. add_tcp ( & listen_addr) ;
6991
70- services. push ( service) ;
71- }
92+ let sni_resolver = SniResolver :: new ( tls_certs) ;
93+ let callback = Box :: new ( sni_resolver) ;
94+ let tls_settings =
95+ TlsSettings :: with_callbacks ( callback) . map_err ( |e| Error :: Proxy ( e. to_string ( ) ) ) ?;
96+ service. add_tls_with_settings ( & listen_addr, None , tls_settings) ;
7297
73- Ok ( services )
98+ Ok ( service )
7499}
75100
76- pub fn plain_routes_services (
101+ pub fn plain_routes_service (
77102 server_conf : Arc < ServerConf > ,
78103 listen_addr : String ,
79- routes : Vec < ProxyRoute > ,
80- ) -> Result < Vec < impl Service > > {
81- let mut services = Vec :: new ( ) ;
82-
83- let plain_routes = routes. into_iter ( ) . filter ( |route| * route. tls == false ) . collect :: < Vec < _ > > ( ) ;
84-
85- for route in plain_routes {
86- let proxy_app = ProxyToUpstream :: new ( route. clone ( ) ) ;
87-
88- let mut service = http_proxy_service ( & server_conf, proxy_app) ;
89- service. add_tcp ( & listen_addr) ;
90-
91- services. push ( service) ;
92- }
93-
94- Ok ( services)
95- }
104+ routes_map : ProxyRouteMap ,
105+ ) -> Result < impl Service > {
106+ let proxy_app = ProxyToUpstream :: new ( routes_map, true ) ;
96107
97- fn http_redirect_service ( listen_addr : String ) -> impl Service {
98- let mut service = ListeningService :: new ( "HTTPS Redirect" . to_string ( ) , RedirectToHttps { } ) ;
108+ let mut service = http_proxy_service ( & server_conf, proxy_app) ;
99109 service. add_tcp ( & listen_addr) ;
100110
101- service
111+ Ok ( service)
102112}
103113
104114pub struct ProxyToUpstream {
105- route : ProxyRoute ,
115+ routes_map : ProxyRouteMap ,
116+ upgrade_to_https : bool ,
106117}
107118
108119impl ProxyToUpstream {
109- pub fn new ( route : ProxyRoute ) -> Self {
120+ pub fn new ( routes_map : ProxyRouteMap , upgrade_to_https : bool ) -> Self {
110121 Self {
111- route,
122+ routes_map,
123+ upgrade_to_https,
112124 }
113125 }
114126}
@@ -118,36 +130,106 @@ impl ProxyHttp for ProxyToUpstream {
118130 type CTX = ( ) ;
119131 fn new_ctx ( & self ) { }
120132
133+ // ProxyToUpstream is shared with both HTTP and HTTPS
134+ // here we do a quick check to upgrade to HTTPS if its a TLS route
135+ async fn request_filter ( & self , session : & mut Session , _ctx : & mut ( ) ) -> PingoraResult < bool > {
136+ if self . upgrade_to_https {
137+ let host = host_from_session ( session)
138+ // 400 bad request no host
139+ . ok_or_else ( || PingoraError :: new ( ErrorType :: HTTPStatus ( 400 ) ) ) ?;
140+
141+ match self . routes_map . get ( host) {
142+ // 421 Misdirected Request doesnt match any hosts
143+ None => {
144+ error ! ( "no route for host: {}" , host) ;
145+ let header = ResponseHeader :: build ( 421 , None ) ?;
146+ session. write_response_header ( Box :: new ( header) , true ) . await ?;
147+ return Ok ( true ) ;
148+ } ,
149+ // 301 Moved Permanently redirect to https
150+ Some ( route) if * route. tls => {
151+ let mut header = ResponseHeader :: build ( 301 , None ) ?;
152+ header. insert_header ( header:: LOCATION , format ! ( "https://{}" , route. host) ) ?;
153+ header. insert_header ( header:: CONTENT_LENGTH , 0 ) ?;
154+
155+ session. write_response_header ( Box :: new ( header) , true ) . await ?;
156+
157+ return Ok ( true ) ;
158+ } ,
159+ // All good let it pass.
160+ Some ( _) => return Ok ( false ) ,
161+ }
162+ }
163+
164+ Ok ( false )
165+ }
166+
121167 async fn upstream_peer (
122168 & self ,
123169 session : & mut Session ,
124170 _ctx : & mut ( ) ,
125171 ) -> PingoraResult < Box < HttpPeer > > {
126- // foxguard: ignore[rs/no-unwrap-in-lib]
127- let host_header = session. get_header ( header:: HOST ) . unwrap ( ) . to_str ( ) . unwrap ( ) ;
172+ let host = host_from_session ( session)
173+ // 400 bad request no host
174+ . ok_or_else ( || PingoraError :: new ( ErrorType :: HTTPStatus ( 400 ) ) ) ?;
128175
129- let proxy_to =
130- HttpPeer :: new ( self . route . upstream . as_str ( ) , false , self . route . host . to_string ( ) ) ;
176+ let route = self
177+ . routes_map
178+ . get ( host)
179+ // 421 Misdirected Request doesnt match any hosts
180+ . ok_or_else ( || PingoraError :: new ( ErrorType :: HTTPStatus ( 421 ) ) ) ?;
181+
182+ let proxy_to = HttpPeer :: new ( route. upstream . as_str ( ) , false , route. host . to_string ( ) ) ;
131183 let peer = Box :: new ( proxy_to) ;
132184 Ok ( peer)
133185 }
134186}
135187
136- pub struct RedirectToHttps ;
188+ // note to self we are not stripping port from the host header
189+ // so Host: example.com:443 will be rejected with a 421
190+ // this is a strict design decision not a bug
191+ fn host_from_session ( session : & Session ) -> Option < & str > {
192+ session. get_header ( header:: HOST ) . and_then ( |h| h. to_str ( ) . ok ( ) )
193+ }
194+
195+ struct SniResolver {
196+ certs : TlsCerts ,
197+ }
198+
199+ impl SniResolver {
200+ fn new ( tls_certs : TlsCerts ) -> Self {
201+ Self {
202+ certs : tls_certs,
203+ }
204+ }
205+ }
137206
138207#[ async_trait]
139- impl ServeHttp for RedirectToHttps {
140- async fn response ( & self , http_stream : & mut ServerSession ) -> Response < Vec < u8 > > {
141- // foxguard: ignore[rs/no-unwrap-in-lib]
142- let host_header = http_stream. get_header ( header:: HOST ) . unwrap ( ) . to_str ( ) . unwrap ( ) ;
143-
144- Response :: builder ( )
145- . status ( StatusCode :: MOVED_PERMANENTLY )
146- . header ( header:: CONTENT_TYPE , "text/html" )
147- . header ( header:: CONTENT_LENGTH , BODY . len ( ) )
148- . header ( header:: LOCATION , format ! ( "https://{host_header}" ) )
149- . body ( BODY . to_owned ( ) )
150- // foxguard: ignore[rs/no-unwrap-in-lib]
151- . unwrap ( )
208+ impl TlsAccept for SniResolver {
209+ async fn certificate_callback ( & self , ssl : & mut ssl:: SslRef ) -> ( ) {
210+ let sni_provided = ssl. servername ( ssl:: NameType :: HOST_NAME ) . map ( str:: to_owned) ;
211+
212+ let Some ( sni_provided) = sni_provided else {
213+ error ! ( "No SNI provided" ) ;
214+ return ;
215+ } ;
216+
217+ let Some ( TlsMaterial {
218+ cert,
219+ key,
220+ } ) = self . certs . get ( sni_provided. as_str ( ) )
221+ else {
222+ error ! ( "No certificate found for SNI: {}" , sni_provided) ;
223+ return ;
224+ } ;
225+
226+ if let Err ( e) = tls:: ext:: ssl_use_certificate ( ssl, cert) {
227+ error ! ( "Failed to use certificate for SNI {}: {}" , sni_provided, e) ;
228+ return ;
229+ }
230+ if let Err ( e) = tls:: ext:: ssl_use_private_key ( ssl, key) {
231+ error ! ( "Failed to use private key for SNI {}: {}" , sni_provided, e) ;
232+ return ;
233+ }
152234 }
153235}
0 commit comments