forked from Deuxfleurs/tricot
Merge pull request 'New directive tricot-add-redirect <match-prefix> <redirect-prefix> [301|302|303|307]
' (#10) from redirect into main
Reviewed-on: Deuxfleurs/tricot#10
This commit is contained in:
commit
b04c2bfb0a
5 changed files with 296 additions and 98 deletions
|
@ -42,6 +42,7 @@ Backends are configured by adding tags of the following form to the services in
|
||||||
- `tricot myapp.example.com/path/to_subresource 10`: combining the previous two examples
|
- `tricot myapp.example.com/path/to_subresource 10`: combining the previous two examples
|
||||||
- `tricot-https myapp.example.com`: same, but indicates that the backend service handling the request expects an HTTPS request and not an HTTP request. In this case, Tricot will do everything in its power to NOT verify the backend's TLS certificate (ignore self-signed certificate, ignore TLS hostname, etc).
|
- `tricot-https myapp.example.com`: same, but indicates that the backend service handling the request expects an HTTPS request and not an HTTP request. In this case, Tricot will do everything in its power to NOT verify the backend's TLS certificate (ignore self-signed certificate, ignore TLS hostname, etc).
|
||||||
- `tricot-add-header Access-Control-Allow-Origin *`: add the `Access-Control-Allow-Origin: *` header to all of the HTTP responses when they are proxied back to the client
|
- `tricot-add-header Access-Control-Allow-Origin *`: add the `Access-Control-Allow-Origin: *` header to all of the HTTP responses when they are proxied back to the client
|
||||||
|
- `tricot-add-redirect old.example.com/maybe_subpath new.example.com/new/subpath 301`: redirects paths that match the first pattern to the second pattern with the given HTTP status code. More info in [PR#10](https://git.deuxfleurs.fr/Deuxfleurs/tricot/pulls/10).
|
||||||
- `tricot-global-lb`: load-balance incoming requests to all matching backends
|
- `tricot-global-lb`: load-balance incoming requests to all matching backends
|
||||||
- `tricot-site-lb`: load-balance incoming requests to all matching backends that are in the same site (geographical location); when site information about nodes is not available, this is equivalent to `tricot-global-lb`
|
- `tricot-site-lb`: load-balance incoming requests to all matching backends that are in the same site (geographical location); when site information about nodes is not available, this is equivalent to `tricot-global-lb`
|
||||||
|
|
||||||
|
|
|
@ -78,7 +78,7 @@ impl CertStore {
|
||||||
|
|
||||||
let proxy_config: Arc<ProxyConfig> = rx_proxy_config.borrow().clone();
|
let proxy_config: Arc<ProxyConfig> = rx_proxy_config.borrow().clone();
|
||||||
for ent in proxy_config.entries.iter() {
|
for ent in proxy_config.entries.iter() {
|
||||||
if let HostDescription::Hostname(domain) = &ent.host {
|
if let HostDescription::Hostname(domain) = &ent.url_prefix.host {
|
||||||
if let Some((host, _port)) = domain.split_once(':') {
|
if let Some((host, _port)) = domain.split_once(':') {
|
||||||
domains.insert(host.to_string());
|
domains.insert(host.to_string());
|
||||||
} else {
|
} else {
|
||||||
|
@ -121,7 +121,7 @@ impl CertStore {
|
||||||
.borrow()
|
.borrow()
|
||||||
.entries
|
.entries
|
||||||
.iter()
|
.iter()
|
||||||
.any(|ent| ent.host.matches(domain))
|
.any(|ent| ent.url_prefix.host.matches(domain))
|
||||||
{
|
{
|
||||||
bail!("Domain {} should not have a TLS certificate.", domain);
|
bail!("Domain {} should not have a TLS certificate.", domain);
|
||||||
}
|
}
|
||||||
|
|
72
src/https.rs
72
src/https.rs
|
@ -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);
|
||||||
|
@ -234,8 +234,9 @@ async fn select_target_and_proxy(
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|ent| {
|
.filter(|ent| {
|
||||||
ent.flags.healthy
|
ent.flags.healthy
|
||||||
&& ent.host.matches(host)
|
&& ent.url_prefix.host.matches(host)
|
||||||
&& ent
|
&& ent
|
||||||
|
.url_prefix
|
||||||
.path_prefix
|
.path_prefix
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|prefix| path.starts_with(prefix))
|
.map(|prefix| path.starts_with(prefix))
|
||||||
|
@ -244,7 +245,8 @@ async fn select_target_and_proxy(
|
||||||
.max_by_key(|ent| {
|
.max_by_key(|ent| {
|
||||||
(
|
(
|
||||||
ent.priority,
|
ent.priority,
|
||||||
ent.path_prefix
|
ent.url_prefix
|
||||||
|
.path_prefix
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|x| x.len() as i32)
|
.map(|x| x.len() as i32)
|
||||||
.unwrap_or(0),
|
.unwrap_or(0),
|
||||||
|
@ -270,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) {
|
||||||
Ok(resp) => resp,
|
// redirection middleware
|
||||||
Err(e) => Response::builder()
|
http_res
|
||||||
.status(StatusCode::BAD_GATEWAY)
|
} else {
|
||||||
.body(Body::from(format!("Proxy error: {}", e)))
|
// proxying to backend
|
||||||
.unwrap(),
|
match do_proxy(https_config, remote_addr, req, proxy_to).await {
|
||||||
|
Ok(resp) => resp,
|
||||||
|
Err(e) => Response::builder()
|
||||||
|
.status(StatusCode::BAD_GATEWAY)
|
||||||
|
.body(Body::from(format!("Proxy error: {}", e)))
|
||||||
|
.unwrap(),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
proxy_to.calls_in_progress.fetch_sub(1, Ordering::SeqCst);
|
proxy_to.calls_in_progress.fetch_sub(1, Ordering::SeqCst);
|
||||||
|
@ -300,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,
|
||||||
|
|
|
@ -239,7 +239,7 @@ async fn dump_config_on_change(
|
||||||
let mut cfg_map = BTreeMap::<_, Vec<_>>::new();
|
let mut cfg_map = BTreeMap::<_, Vec<_>>::new();
|
||||||
for ent in cfg.entries.iter() {
|
for ent in cfg.entries.iter() {
|
||||||
cfg_map
|
cfg_map
|
||||||
.entry((&ent.host, &ent.path_prefix))
|
.entry((&ent.url_prefix.host, &ent.url_prefix.path_prefix))
|
||||||
.or_default()
|
.or_default()
|
||||||
.push(ent);
|
.push(ent);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ use crate::consul;
|
||||||
|
|
||||||
// ---- Extract proxy config from Consul catalog ----
|
// ---- Extract proxy config from Consul catalog ----
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
pub enum HostDescription {
|
pub enum HostDescription {
|
||||||
Hostname(String),
|
Hostname(String),
|
||||||
Pattern(glob::Pattern),
|
Pattern(glob::Pattern),
|
||||||
|
@ -45,12 +45,48 @@ impl std::fmt::Display for HostDescription {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ProxyEntry {
|
pub struct UrlPrefix {
|
||||||
/// Publicly exposed TLS hostnames for matching this rule
|
/// Publicly exposed TLS hostnames for matching this rule
|
||||||
pub host: HostDescription,
|
pub host: HostDescription,
|
||||||
|
|
||||||
/// Path prefix for matching this rule
|
/// Path prefix for matching this rule
|
||||||
pub path_prefix: Option<String>,
|
pub path_prefix: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for UrlPrefix {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.host == other.host && self.path_prefix == other.path_prefix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Eq for UrlPrefix {}
|
||||||
|
|
||||||
|
impl UrlPrefix {
|
||||||
|
fn new(raw_prefix: &str) -> Option<Self> {
|
||||||
|
let (raw_host, path_prefix) = match raw_prefix.find('/') {
|
||||||
|
Some(i) => {
|
||||||
|
let (host, pp) = raw_prefix.split_at(i);
|
||||||
|
(host, Some(pp.to_string()))
|
||||||
|
}
|
||||||
|
None => (raw_prefix, None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let host = match HostDescription::new(raw_host) {
|
||||||
|
Ok(h) => h,
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Invalid hostname pattern {}: {}", raw_host, e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(Self { host, path_prefix })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ProxyEntry {
|
||||||
|
/// An Url prefix is made of a host and maybe a path prefix
|
||||||
|
pub url_prefix: UrlPrefix,
|
||||||
/// Priority with which this rule is considered (highest first)
|
/// Priority with which this rule is considered (highest first)
|
||||||
pub priority: u32,
|
pub priority: u32,
|
||||||
|
|
||||||
|
@ -68,6 +104,10 @@ pub struct ProxyEntry {
|
||||||
/// when matching this rule
|
/// when matching this rule
|
||||||
pub add_headers: Vec<(String, String)>,
|
pub add_headers: Vec<(String, String)>,
|
||||||
|
|
||||||
|
/// Try to match all these redirection before forwarding to the backend
|
||||||
|
/// when matching this rule
|
||||||
|
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,
|
||||||
/// Time of last call, used for round-robin selection
|
/// Time of last call, used for round-robin selection
|
||||||
|
@ -76,8 +116,7 @@ pub struct ProxyEntry {
|
||||||
|
|
||||||
impl PartialEq for ProxyEntry {
|
impl PartialEq for ProxyEntry {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.host == other.host
|
self.url_prefix == other.url_prefix
|
||||||
&& self.path_prefix == other.path_prefix
|
|
||||||
&& self.priority == other.priority
|
&& self.priority == other.priority
|
||||||
&& self.service_name == other.service_name
|
&& self.service_name == other.service_name
|
||||||
&& self.target_addr == other.target_addr
|
&& self.target_addr == other.target_addr
|
||||||
|
@ -88,6 +127,52 @@ impl PartialEq for ProxyEntry {
|
||||||
}
|
}
|
||||||
impl Eq for ProxyEntry {}
|
impl Eq for ProxyEntry {}
|
||||||
|
|
||||||
|
impl ProxyEntry {
|
||||||
|
fn new(
|
||||||
|
service_name: String,
|
||||||
|
frontend: MatchTag,
|
||||||
|
target_addr: SocketAddr,
|
||||||
|
middleware: &[ConfigTag],
|
||||||
|
flags: ProxyEntryFlags,
|
||||||
|
) -> Self {
|
||||||
|
let (url_prefix, priority, https_target) = match frontend {
|
||||||
|
MatchTag::Http(u, p) => (u, p, false),
|
||||||
|
MatchTag::HttpWithTls(u, p) => (u, p, true),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut add_headers = vec![];
|
||||||
|
let mut redirects = vec![];
|
||||||
|
for mid in middleware.into_iter() {
|
||||||
|
match mid {
|
||||||
|
ConfigTag::AddHeader(k, v) => add_headers.push((k.to_string(), v.clone())),
|
||||||
|
ConfigTag::AddRedirect(m, r, c) => redirects.push(((*m).clone(), (*r).clone(), *c)),
|
||||||
|
ConfigTag::LocalLb | ConfigTag::GlobalLb => {
|
||||||
|
/* handled in parent fx */
|
||||||
|
()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ProxyEntry {
|
||||||
|
// id
|
||||||
|
service_name,
|
||||||
|
// frontend
|
||||||
|
url_prefix,
|
||||||
|
priority,
|
||||||
|
// backend
|
||||||
|
target_addr,
|
||||||
|
https_target,
|
||||||
|
// middleware
|
||||||
|
flags,
|
||||||
|
add_headers,
|
||||||
|
redirects,
|
||||||
|
// internal
|
||||||
|
last_call: atomic::AtomicI64::from(0),
|
||||||
|
calls_in_progress: atomic::AtomicI64::from(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
pub struct ProxyEntryFlags {
|
pub struct ProxyEntryFlags {
|
||||||
/// Is the target healthy?
|
/// Is the target healthy?
|
||||||
|
@ -115,8 +200,8 @@ impl std::fmt::Display for ProxyEntry {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"{}{} {}",
|
"{}{} {}",
|
||||||
self.host,
|
self.url_prefix.host,
|
||||||
self.path_prefix.as_deref().unwrap_or_default(),
|
self.url_prefix.path_prefix.as_deref().unwrap_or_default(),
|
||||||
self.priority
|
self.priority
|
||||||
)?;
|
)?;
|
||||||
if !self.flags.healthy {
|
if !self.flags.healthy {
|
||||||
|
@ -144,62 +229,105 @@ pub struct ProxyConfig {
|
||||||
pub entries: Vec<ProxyEntry>,
|
pub entries: Vec<ProxyEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_tricot_tag(
|
#[derive(Debug)]
|
||||||
service_name: String,
|
enum ParsedTag<'a> {
|
||||||
tag: &str,
|
Frontend(MatchTag),
|
||||||
target_addr: SocketAddr,
|
Middleware(ConfigTag<'a>),
|
||||||
add_headers: &[(String, String)],
|
|
||||||
flags: ProxyEntryFlags,
|
|
||||||
) -> Option<ProxyEntry> {
|
|
||||||
let splits = tag.split(' ').collect::<Vec<_>>();
|
|
||||||
if (splits.len() != 2 && splits.len() != 3)
|
|
||||||
|| (splits[0] != "tricot" && splits[0] != "tricot-https")
|
|
||||||
{
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let (host, path_prefix) = match splits[1].find('/') {
|
|
||||||
Some(i) => {
|
|
||||||
let (host, pp) = splits[1].split_at(i);
|
|
||||||
(host, Some(pp.to_string()))
|
|
||||||
}
|
|
||||||
None => (splits[1], None),
|
|
||||||
};
|
|
||||||
|
|
||||||
let priority = match splits.len() {
|
|
||||||
3 => splits[2].parse().ok()?,
|
|
||||||
_ => 100,
|
|
||||||
};
|
|
||||||
|
|
||||||
let host = match HostDescription::new(host) {
|
|
||||||
Ok(h) => h,
|
|
||||||
Err(e) => {
|
|
||||||
warn!("Invalid hostname pattern {}: {}", host, e);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(ProxyEntry {
|
|
||||||
service_name,
|
|
||||||
target_addr,
|
|
||||||
https_target: (splits[0] == "tricot-https"),
|
|
||||||
host,
|
|
||||||
flags,
|
|
||||||
path_prefix,
|
|
||||||
priority,
|
|
||||||
add_headers: add_headers.to_vec(),
|
|
||||||
last_call: atomic::AtomicI64::from(0),
|
|
||||||
calls_in_progress: atomic::AtomicI64::from(0),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_tricot_add_header_tag(tag: &str) -> Option<(String, String)> {
|
#[derive(Debug)]
|
||||||
let splits = tag.splitn(3, ' ').collect::<Vec<_>>();
|
enum MatchTag {
|
||||||
if splits.len() == 3 && splits[0] == "tricot-add-header" {
|
/// HTTP backend (plain text)
|
||||||
Some((splits[1].to_string(), splits[2].to_string()))
|
Http(UrlPrefix, u32),
|
||||||
} else {
|
/// HTTPS backend (TLS encrypted)
|
||||||
None
|
HttpWithTls(UrlPrefix, u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum ConfigTag<'a> {
|
||||||
|
AddHeader(&'a str, String),
|
||||||
|
AddRedirect(UrlPrefix, UrlPrefix, u16),
|
||||||
|
GlobalLb,
|
||||||
|
LocalLb,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_tricot_tags(tag: &str) -> Option<ParsedTag> {
|
||||||
|
let splits = tag.splitn(4, ' ').collect::<Vec<_>>();
|
||||||
|
let parsed_tag = match splits.as_slice() {
|
||||||
|
["tricot", raw_prefix, maybe_priority @ ..] => {
|
||||||
|
// priority is set to 100 when value is invalid or missing
|
||||||
|
let priority: u32 = maybe_priority
|
||||||
|
.iter()
|
||||||
|
.next()
|
||||||
|
.map_or(Ok(100), |x| x.parse::<u32>())
|
||||||
|
.unwrap_or(100);
|
||||||
|
UrlPrefix::new(raw_prefix)
|
||||||
|
.map(|prefix| ParsedTag::Frontend(MatchTag::Http(prefix, priority)))
|
||||||
|
}
|
||||||
|
["tricot-https", raw_prefix, maybe_priority @ ..] => {
|
||||||
|
// priority is set to 100 when value is invalid or missing
|
||||||
|
let priority: u32 = maybe_priority
|
||||||
|
.iter()
|
||||||
|
.next()
|
||||||
|
.map_or(Ok(100), |x| x.parse::<u32>())
|
||||||
|
.unwrap_or(100);
|
||||||
|
UrlPrefix::new(raw_prefix)
|
||||||
|
.map(|prefix| ParsedTag::Frontend(MatchTag::HttpWithTls(prefix, priority)))
|
||||||
|
}
|
||||||
|
["tricot-add-header", header_key, header_values @ ..] => Some(ParsedTag::Middleware(
|
||||||
|
ConfigTag::AddHeader(header_key, header_values.join(" ")),
|
||||||
|
)),
|
||||||
|
["tricot-add-redirect", raw_match, raw_replace, maybe_raw_code @ ..] => {
|
||||||
|
let (p_match, p_replace) =
|
||||||
|
match (UrlPrefix::new(raw_match), UrlPrefix::new(raw_replace)) {
|
||||||
|
(Some(m), Some(r)) => (m, r),
|
||||||
|
_ => {
|
||||||
|
debug!(
|
||||||
|
"tag {} is ignored, one of the url prefix can't be parsed",
|
||||||
|
tag
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if matches!(p_replace.host, HostDescription::Pattern(_)) {
|
||||||
|
debug!(
|
||||||
|
"tag {} ignored as redirect to a glob pattern is not supported",
|
||||||
|
tag
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let maybe_parsed_code = maybe_raw_code
|
||||||
|
.iter()
|
||||||
|
.next()
|
||||||
|
.map(|c| c.parse::<u16>().ok())
|
||||||
|
.flatten();
|
||||||
|
let http_code = match maybe_parsed_code {
|
||||||
|
Some(301) => 301,
|
||||||
|
Some(302) => 302,
|
||||||
|
Some(303) => 303,
|
||||||
|
Some(307) => 307,
|
||||||
|
_ => {
|
||||||
|
debug!(
|
||||||
|
"tag {} has a missing or invalid http code, setting it to 302",
|
||||||
|
tag
|
||||||
|
);
|
||||||
|
302
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(ParsedTag::Middleware(ConfigTag::AddRedirect(
|
||||||
|
p_match, p_replace, http_code,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
["tricot-global-lb", ..] => Some(ParsedTag::Middleware(ConfigTag::GlobalLb)),
|
||||||
|
["tricot-local-lb", ..] => Some(ParsedTag::Middleware(ConfigTag::LocalLb)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
trace!("tag {} parsed as {:?}", tag, parsed_tag);
|
||||||
|
parsed_tag
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_consul_service(
|
fn parse_consul_service(
|
||||||
|
@ -208,8 +336,6 @@ fn parse_consul_service(
|
||||||
) -> Vec<ProxyEntry> {
|
) -> Vec<ProxyEntry> {
|
||||||
trace!("Parsing service: {:#?}", s);
|
trace!("Parsing service: {:#?}", s);
|
||||||
|
|
||||||
let mut entries = vec![];
|
|
||||||
|
|
||||||
let ip_addr = match s.service.address.parse() {
|
let ip_addr = match s.service.address.parse() {
|
||||||
Ok(ip) => ip,
|
Ok(ip) => ip,
|
||||||
_ => match s.node.address.parse() {
|
_ => match s.node.address.parse() {
|
||||||
|
@ -225,31 +351,48 @@ fn parse_consul_service(
|
||||||
};
|
};
|
||||||
let addr = SocketAddr::new(ip_addr, s.service.port);
|
let addr = SocketAddr::new(ip_addr, s.service.port);
|
||||||
|
|
||||||
if s.service.tags.contains(&"tricot-global-lb".into()) {
|
// tag parsing
|
||||||
flags.global_lb = true;
|
let mut collected_middleware = vec![];
|
||||||
} else if s.service.tags.contains(&"tricot-site-lb".into()) {
|
let mut collected_frontends = vec![];
|
||||||
flags.site_lb = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut add_headers = vec![];
|
|
||||||
for tag in s.service.tags.iter() {
|
for tag in s.service.tags.iter() {
|
||||||
if let Some(pair) = parse_tricot_add_header_tag(tag) {
|
match parse_tricot_tags(tag) {
|
||||||
add_headers.push(pair);
|
Some(ParsedTag::Frontend(x)) => collected_frontends.push(x),
|
||||||
|
Some(ParsedTag::Middleware(y)) => collected_middleware.push(y),
|
||||||
|
_ => trace!(
|
||||||
|
"service {}: tag '{}' could not be parsed",
|
||||||
|
s.service.service,
|
||||||
|
tag
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for tag in s.service.tags.iter() {
|
// some legacy processing that would need a refactor later
|
||||||
if let Some(ent) = parse_tricot_tag(
|
for mid in collected_middleware.iter() {
|
||||||
s.service.service.clone(),
|
match mid {
|
||||||
tag,
|
ConfigTag::AddHeader(_, _) | ConfigTag::AddRedirect(_, _, _) =>
|
||||||
addr,
|
/* not handled here */
|
||||||
&add_headers[..],
|
{
|
||||||
flags,
|
()
|
||||||
) {
|
}
|
||||||
entries.push(ent);
|
ConfigTag::GlobalLb => flags.global_lb = true,
|
||||||
}
|
ConfigTag::LocalLb => flags.site_lb = true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// build proxy entries
|
||||||
|
let entries = collected_frontends
|
||||||
|
.into_iter()
|
||||||
|
.map(|frt| {
|
||||||
|
ProxyEntry::new(
|
||||||
|
s.service.service.clone(),
|
||||||
|
frt,
|
||||||
|
addr,
|
||||||
|
collected_middleware.as_ref(),
|
||||||
|
flags,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
trace!("Result of parsing service:");
|
trace!("Result of parsing service:");
|
||||||
for ent in entries.iter() {
|
for ent in entries.iter() {
|
||||||
trace!(" {}", ent);
|
trace!(" {}", ent);
|
||||||
|
@ -347,8 +490,8 @@ impl ProxyConfigMetrics {
|
||||||
let mut patterns = HashMap::new();
|
let mut patterns = HashMap::new();
|
||||||
for ent in rx.borrow().entries.iter() {
|
for ent in rx.borrow().entries.iter() {
|
||||||
let attrs = (
|
let attrs = (
|
||||||
ent.host.to_string(),
|
ent.url_prefix.host.to_string(),
|
||||||
ent.path_prefix.clone().unwrap_or_default(),
|
ent.url_prefix.path_prefix.clone().unwrap_or_default(),
|
||||||
ent.service_name.clone(),
|
ent.service_name.clone(),
|
||||||
);
|
);
|
||||||
*patterns.entry(attrs).or_default() += 1;
|
*patterns.entry(attrs).or_default() += 1;
|
||||||
|
@ -378,8 +521,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_tricot_add_header_tag() {
|
fn test_parse_tricot_add_header_tag() {
|
||||||
match parse_tricot_add_header_tag("tricot-add-header Content-Security-Policy default-src 'none'; img-src 'self'; script-src 'self'; style-src 'self'") {
|
match parse_tricot_tags("tricot-add-header Content-Security-Policy default-src 'none'; img-src 'self'; script-src 'self'; style-src 'self'") {
|
||||||
Some((name, value)) => {
|
Some(ParsedTag::Middleware(ConfigTag::AddHeader(name, value))) => {
|
||||||
assert_eq!(name, "Content-Security-Policy");
|
assert_eq!(name, "Content-Security-Policy");
|
||||||
assert_eq!(value, "default-src 'none'; img-src 'self'; script-src 'self'; style-src 'self'");
|
assert_eq!(value, "default-src 'none'; img-src 'self'; script-src 'self'; style-src 'self'");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue