implement redirection in https.rs

This commit is contained in:
Quentin 2023-11-29 15:50:25 +01:00
parent f11592926b
commit 2b3f934247
Signed by untrusted user: quentin
GPG key ID: E9602264D639FF68
2 changed files with 65 additions and 11 deletions

View file

@ -24,7 +24,7 @@ use tokio_util::io::{ReaderStream, StreamReader};
use opentelemetry::{metrics, KeyValue}; use opentelemetry::{metrics, KeyValue};
use crate::cert_store::{CertStore, StoreResolver}; use crate::cert_store::{CertStore, StoreResolver};
use crate::proxy_config::{ProxyConfig, ProxyEntry}; use crate::proxy_config::{HostDescription, ProxyConfig, ProxyEntry};
use crate::reverse_proxy; use crate::reverse_proxy;
const MAX_CONNECTION_LIFETIME: Duration = Duration::from_secs(24 * 3600); const MAX_CONNECTION_LIFETIME: Duration = Duration::from_secs(24 * 3600);
@ -272,15 +272,22 @@ async fn select_target_and_proxy(
); );
proxy_to.calls_in_progress.fetch_add(1, Ordering::SeqCst); proxy_to.calls_in_progress.fetch_add(1, Ordering::SeqCst);
// Forward to backend
debug!("{}{} -> {}", host, path, proxy_to); debug!("{}{} -> {}", host, path, proxy_to);
trace!("Request: {:?}", req); trace!("Request: {:?}", req);
let response = match do_proxy(https_config, remote_addr, req, proxy_to).await { let response = if let Some(http_res) = try_redirect(host, path, proxy_to) {
// redirection middleware
http_res
} else {
// proxying to backend
match do_proxy(https_config, remote_addr, req, proxy_to).await {
Ok(resp) => resp, Ok(resp) => resp,
Err(e) => Response::builder() Err(e) => Response::builder()
.status(StatusCode::BAD_GATEWAY) .status(StatusCode::BAD_GATEWAY)
.body(Body::from(format!("Proxy error: {}", e))) .body(Body::from(format!("Proxy error: {}", e)))
.unwrap(), .unwrap(),
}
}; };
proxy_to.calls_in_progress.fetch_sub(1, Ordering::SeqCst); proxy_to.calls_in_progress.fetch_sub(1, Ordering::SeqCst);
@ -302,6 +309,51 @@ async fn select_target_and_proxy(
} }
} }
fn try_redirect(req_host: &str, req_path: &str, proxy_to: &ProxyEntry) -> Option<Response<Body>> {
let maybe_redirect = proxy_to.redirects.iter().find(|(src, _, _)| {
let mut matched: bool = src.host.matches(req_host);
if let Some(path) = &src.path_prefix {
matched &= req_path.starts_with(path);
}
matched
});
let (src_prefix, dst_prefix, code) = match maybe_redirect {
None => return None,
Some(redirect) => redirect,
};
let new_host = match &dst_prefix.host {
HostDescription::Hostname(h) => h,
_ => unreachable!(), // checked when ProxyEntry is created
};
let new_prefix = dst_prefix.path_prefix.as_deref().unwrap_or("");
let original_prefix = src_prefix.path_prefix.as_deref().unwrap_or("");
let suffix = &req_path[original_prefix.len()..];
let uri = format!("https://{}{}{}", new_host, new_prefix, suffix);
let status = match StatusCode::from_u16(*code) {
Err(e) => {
warn!(
"Couldn't redirect {}{} to {} as code {} in invalid: {}",
req_host, req_path, uri, code, e
);
return None;
}
Ok(sc) => sc,
};
Response::builder()
.header("Location", uri.clone())
.status(status)
.body(Body::from(uri))
.ok()
}
async fn do_proxy( async fn do_proxy(
https_config: &HttpsConfig, https_config: &HttpsConfig,
remote_addr: SocketAddr, remote_addr: SocketAddr,

View file

@ -106,7 +106,7 @@ pub struct ProxyEntry {
/// Try to match all these redirection before forwarding to the backend /// Try to match all these redirection before forwarding to the backend
/// when matching this rule /// when matching this rule
pub redirects: Vec<(UrlPrefix, UrlPrefix, u32)>, pub redirects: Vec<(UrlPrefix, UrlPrefix, u16)>,
/// Number of calls in progress, used to deprioritize slow back-ends /// Number of calls in progress, used to deprioritize slow back-ends
pub calls_in_progress: atomic::AtomicI64, pub calls_in_progress: atomic::AtomicI64,
@ -246,7 +246,7 @@ enum MatchTag {
#[derive(Debug)] #[derive(Debug)]
enum ConfigTag<'a> { enum ConfigTag<'a> {
AddHeader(&'a str, String), AddHeader(&'a str, String),
AddRedirect(UrlPrefix, UrlPrefix, u32), AddRedirect(UrlPrefix, UrlPrefix, u16),
GlobalLb, GlobalLb,
LocalLb, LocalLb,
} }
@ -301,14 +301,16 @@ fn parse_tricot_tags(tag: &str) -> Option<ParsedTag> {
let maybe_parsed_code = maybe_raw_code let maybe_parsed_code = maybe_raw_code
.iter() .iter()
.next() .next()
.map(|c| c.parse::<u32>().ok()) .map(|c| c.parse::<u16>().ok())
.flatten(); .flatten();
let http_code = match maybe_parsed_code { let http_code = match maybe_parsed_code {
Some(301) => 301, Some(301) => 301,
Some(302) => 302, Some(302) => 302,
Some(303) => 303,
Some(307) => 307,
_ => { _ => {
debug!( debug!(
"tag {} has a missing or invalid http code, set it to 302", "tag {} has a missing or invalid http code, setting it to 302",
tag tag
); );
302 302