mirror of
https://github.com/awfufu/traudit
synced 2026-03-01 05:29:44 +08:00
feat: add configurable HTTP-to-HTTPS redirects with redirect-only HTTP services
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -4059,7 +4059,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "traudit"
|
name = "traudit"
|
||||||
version = "0.0.8"
|
version = "0.0.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "traudit"
|
name = "traudit"
|
||||||
version = "0.0.8"
|
version = "0.0.9"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["awfufu"]
|
authors = ["awfufu"]
|
||||||
description = "A reverse proxy that streams audit records directly to databases."
|
description = "A reverse proxy that streams audit records directly to databases."
|
||||||
|
|||||||
@@ -39,11 +39,73 @@ pub struct ServiceConfig {
|
|||||||
pub service_type: String,
|
pub service_type: String,
|
||||||
pub binds: Vec<BindEntry>,
|
pub binds: Vec<BindEntry>,
|
||||||
#[serde(rename = "forward_to")]
|
#[serde(rename = "forward_to")]
|
||||||
pub forward_to: String,
|
pub forward_to: Option<String>,
|
||||||
#[serde(rename = "upstream_proxy")]
|
#[serde(rename = "upstream_proxy")]
|
||||||
pub upstream_proxy: Option<String>,
|
pub upstream_proxy: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct RedirectHttpsConfig {
|
||||||
|
pub enabled: bool,
|
||||||
|
pub code: u16,
|
||||||
|
pub port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct RedirectHttpsConfigObject {
|
||||||
|
enabled: bool,
|
||||||
|
#[serde(default = "default_redirect_code")]
|
||||||
|
code: u16,
|
||||||
|
#[serde(default = "default_redirect_port")]
|
||||||
|
port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum RedirectHttpsConfigRaw {
|
||||||
|
Bool(bool),
|
||||||
|
Object(RedirectHttpsConfigObject),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RedirectHttpsConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
enabled: false,
|
||||||
|
code: default_redirect_code(),
|
||||||
|
port: default_redirect_port(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for RedirectHttpsConfig {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let raw = RedirectHttpsConfigRaw::deserialize(deserializer)?;
|
||||||
|
Ok(match raw {
|
||||||
|
RedirectHttpsConfigRaw::Bool(enabled) => Self {
|
||||||
|
enabled,
|
||||||
|
code: default_redirect_code(),
|
||||||
|
port: default_redirect_port(),
|
||||||
|
},
|
||||||
|
RedirectHttpsConfigRaw::Object(obj) => Self {
|
||||||
|
enabled: obj.enabled,
|
||||||
|
code: obj.code,
|
||||||
|
port: obj.port,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_redirect_code() -> u16 {
|
||||||
|
308
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_redirect_port() -> u16 {
|
||||||
|
443
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct RealIpConfig {
|
pub struct RealIpConfig {
|
||||||
#[serde(default, rename = "from")]
|
#[serde(default, rename = "from")]
|
||||||
@@ -103,6 +165,8 @@ pub struct BindEntry {
|
|||||||
pub tls: Option<TlsConfig>,
|
pub tls: Option<TlsConfig>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub add_xff_header: bool,
|
pub add_xff_header: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub redirect_https: RedirectHttpsConfig,
|
||||||
pub real_ip: Option<RealIpConfig>,
|
pub real_ip: Option<RealIpConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +255,48 @@ impl Config {
|
|||||||
|
|
||||||
pub fn validate(&self) -> anyhow::Result<()> {
|
pub fn validate(&self) -> anyhow::Result<()> {
|
||||||
for service in &self.services {
|
for service in &self.services {
|
||||||
|
let needs_backend = match service.service_type.as_str() {
|
||||||
|
"tcp" => true,
|
||||||
|
"http" => service.binds.iter().any(|b| !b.redirect_https.enabled),
|
||||||
|
_ => true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if needs_backend && service.forward_to.is_none() {
|
||||||
|
anyhow::bail!(
|
||||||
|
"Service '{}' requires 'forward_to'. For type '{}' this is required unless all HTTP binds are redirect-only.",
|
||||||
|
service.name,
|
||||||
|
service.service_type
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
for bind in &service.binds {
|
for bind in &service.binds {
|
||||||
|
if bind.redirect_https.enabled {
|
||||||
|
if service.service_type != "http" {
|
||||||
|
anyhow::bail!(
|
||||||
|
"Service '{}' bind '{}' enables 'redirect_https', but this is only valid for type 'http'.",
|
||||||
|
service.name,
|
||||||
|
bind.addr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if bind.tls.is_some() {
|
||||||
|
anyhow::bail!(
|
||||||
|
"Service '{}' bind '{}' enables 'redirect_https' and 'tls' together. Redirect-to-HTTPS must be configured on non-TLS HTTP binds.",
|
||||||
|
service.name,
|
||||||
|
bind.addr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(300..=399).contains(&bind.redirect_https.code) {
|
||||||
|
anyhow::bail!(
|
||||||
|
"Service '{}' bind '{}' has invalid 'redirect_https.code' {}. Expected 3xx status code.",
|
||||||
|
service.name,
|
||||||
|
bind.addr,
|
||||||
|
bind.redirect_https.code
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(real_ip) = &bind.real_ip {
|
if let Some(real_ip) = &bind.real_ip {
|
||||||
// Rule 1: TCP services cannot use XFF as they don't parse HTTP headers
|
// Rule 1: TCP services cannot use XFF as they don't parse HTTP headers
|
||||||
if service.service_type == "tcp" && real_ip.source == RealIpSource::Xff {
|
if service.service_type == "tcp" && real_ip.source == RealIpSource::Xff {
|
||||||
@@ -227,6 +332,13 @@ impl Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(upstream_proxy) = &service.upstream_proxy {
|
if let Some(upstream_proxy) = &service.upstream_proxy {
|
||||||
|
if service.forward_to.is_none() {
|
||||||
|
anyhow::bail!(
|
||||||
|
"Service '{}' sets 'upstream_proxy' but has no 'forward_to'.",
|
||||||
|
service.name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
match upstream_proxy.as_str() {
|
match upstream_proxy.as_str() {
|
||||||
"v1" | "v2" => {},
|
"v1" | "v2" => {},
|
||||||
other => anyhow::bail!(
|
other => anyhow::bail!(
|
||||||
@@ -277,10 +389,147 @@ services:
|
|||||||
assert_eq!(config.services[0].name, "ssh-prod");
|
assert_eq!(config.services[0].name, "ssh-prod");
|
||||||
assert_eq!(config.services[0].binds[0].addr, "0.0.0.0:22222");
|
assert_eq!(config.services[0].binds[0].addr, "0.0.0.0:22222");
|
||||||
assert_eq!(config.services[0].binds[0].proxy, Some("v2".to_string()));
|
assert_eq!(config.services[0].binds[0].proxy, Some("v2".to_string()));
|
||||||
assert_eq!(config.services[0].forward_to, "127.0.0.1:22");
|
assert_eq!(config.services[0].forward_to, Some("127.0.0.1:22".to_string()));
|
||||||
assert_eq!(config.services[0].upstream_proxy, None);
|
assert_eq!(config.services[0].upstream_proxy, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_redirect_https_bool_and_object() {
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct TestBind {
|
||||||
|
#[serde(default)]
|
||||||
|
redirect_https: RedirectHttpsConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
let yaml_bool = "redirect_https: true";
|
||||||
|
let bind_bool: TestBind = serde_yaml::from_str(yaml_bool).unwrap();
|
||||||
|
assert!(bind_bool.redirect_https.enabled);
|
||||||
|
assert_eq!(bind_bool.redirect_https.code, 308);
|
||||||
|
assert_eq!(bind_bool.redirect_https.port, 443);
|
||||||
|
|
||||||
|
let yaml_obj = "redirect_https:\n enabled: true\n code: 301\n port: 8443\n";
|
||||||
|
let bind_obj: TestBind = serde_yaml::from_str(yaml_obj).unwrap();
|
||||||
|
assert!(bind_obj.redirect_https.enabled);
|
||||||
|
assert_eq!(bind_obj.redirect_https.code, 301);
|
||||||
|
assert_eq!(bind_obj.redirect_https.port, 8443);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_http_redirect_only_can_omit_forward_to() {
|
||||||
|
let config_str = r#"
|
||||||
|
database:
|
||||||
|
type: clickhouse
|
||||||
|
dsn: "clickhouse://admin:password@127.0.0.1:8123/audit_db"
|
||||||
|
|
||||||
|
services:
|
||||||
|
- name: "redirect-only"
|
||||||
|
type: "http"
|
||||||
|
binds:
|
||||||
|
- addr: "0.0.0.0:80"
|
||||||
|
redirect_https: true
|
||||||
|
"#;
|
||||||
|
let mut file = tempfile::NamedTempFile::new().unwrap();
|
||||||
|
write!(file, "{}", config_str).unwrap();
|
||||||
|
let path = file.path().to_path_buf();
|
||||||
|
|
||||||
|
let config = Config::load(&path).await.expect("Failed to load config");
|
||||||
|
assert_eq!(config.services[0].forward_to, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_http_non_redirect_bind_requires_forward_to() {
|
||||||
|
let config_str = r#"
|
||||||
|
database:
|
||||||
|
type: clickhouse
|
||||||
|
dsn: "clickhouse://admin:password@127.0.0.1:8123/audit_db"
|
||||||
|
|
||||||
|
services:
|
||||||
|
- name: "http-no-backend"
|
||||||
|
type: "http"
|
||||||
|
binds:
|
||||||
|
- addr: "0.0.0.0:8080"
|
||||||
|
"#;
|
||||||
|
let mut file = tempfile::NamedTempFile::new().unwrap();
|
||||||
|
write!(file, "{}", config_str).unwrap();
|
||||||
|
let path = file.path().to_path_buf();
|
||||||
|
|
||||||
|
let err = Config::load(&path).await.unwrap_err();
|
||||||
|
assert!(err.to_string().contains("requires 'forward_to'"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_redirect_https_rejects_tls_same_bind() {
|
||||||
|
let config_str = r#"
|
||||||
|
database:
|
||||||
|
type: clickhouse
|
||||||
|
dsn: "clickhouse://admin:password@127.0.0.1:8123/audit_db"
|
||||||
|
|
||||||
|
services:
|
||||||
|
- name: "bad-redirect-tls"
|
||||||
|
type: "http"
|
||||||
|
binds:
|
||||||
|
- addr: "0.0.0.0:443"
|
||||||
|
tls:
|
||||||
|
cert: "/tmp/cert.pem"
|
||||||
|
key: "/tmp/key.pem"
|
||||||
|
redirect_https: true
|
||||||
|
forward_to: "127.0.0.1:8080"
|
||||||
|
"#;
|
||||||
|
let mut file = tempfile::NamedTempFile::new().unwrap();
|
||||||
|
write!(file, "{}", config_str).unwrap();
|
||||||
|
let path = file.path().to_path_buf();
|
||||||
|
|
||||||
|
let err = Config::load(&path).await.unwrap_err();
|
||||||
|
assert!(err.to_string().contains("'redirect_https' and 'tls' together"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_redirect_https_requires_http_service() {
|
||||||
|
let config_str = r#"
|
||||||
|
database:
|
||||||
|
type: clickhouse
|
||||||
|
dsn: "clickhouse://admin:password@127.0.0.1:8123/audit_db"
|
||||||
|
|
||||||
|
services:
|
||||||
|
- name: "bad-redirect-tcp"
|
||||||
|
type: "tcp"
|
||||||
|
binds:
|
||||||
|
- addr: "0.0.0.0:2222"
|
||||||
|
redirect_https: true
|
||||||
|
forward_to: "127.0.0.1:22"
|
||||||
|
"#;
|
||||||
|
let mut file = tempfile::NamedTempFile::new().unwrap();
|
||||||
|
write!(file, "{}", config_str).unwrap();
|
||||||
|
let path = file.path().to_path_buf();
|
||||||
|
|
||||||
|
let err = Config::load(&path).await.unwrap_err();
|
||||||
|
assert!(err.to_string().contains("only valid for type 'http'"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_redirect_https_code_must_be_3xx() {
|
||||||
|
let config_str = r#"
|
||||||
|
database:
|
||||||
|
type: clickhouse
|
||||||
|
dsn: "clickhouse://admin:password@127.0.0.1:8123/audit_db"
|
||||||
|
|
||||||
|
services:
|
||||||
|
- name: "bad-redirect-code"
|
||||||
|
type: "http"
|
||||||
|
binds:
|
||||||
|
- addr: "0.0.0.0:80"
|
||||||
|
redirect_https:
|
||||||
|
enabled: true
|
||||||
|
code: 200
|
||||||
|
"#;
|
||||||
|
let mut file = tempfile::NamedTempFile::new().unwrap();
|
||||||
|
write!(file, "{}", config_str).unwrap();
|
||||||
|
let path = file.path().to_path_buf();
|
||||||
|
|
||||||
|
let err = Config::load(&path).await.unwrap_err();
|
||||||
|
assert!(err.to_string().contains("Expected 3xx status code"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_mode_deserialization() {
|
fn test_mode_deserialization() {
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use crate::config::{RealIpSource, ServiceConfig};
|
use crate::config::{RealIpSource, RedirectHttpsConfig, ServiceConfig};
|
||||||
use crate::db::clickhouse::{ClickHouseLogger, HttpLog, HttpMethod};
|
use crate::db::clickhouse::{ClickHouseLogger, HttpLog, HttpMethod};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use pingora::prelude::*;
|
use pingora::prelude::*;
|
||||||
|
use pingora::http::ResponseHeader;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
@@ -12,6 +13,7 @@ pub struct TrauditProxy {
|
|||||||
pub listen_addr: String,
|
pub listen_addr: String,
|
||||||
pub real_ip: Option<crate::config::RealIpConfig>,
|
pub real_ip: Option<crate::config::RealIpConfig>,
|
||||||
pub add_xff_header: bool,
|
pub add_xff_header: bool,
|
||||||
|
pub redirect_https: RedirectHttpsConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct HttpContext {
|
pub struct HttpContext {
|
||||||
@@ -237,6 +239,20 @@ impl ProxyHttp for TrauditProxy {
|
|||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
|
if self.redirect_https.enabled {
|
||||||
|
let location = build_https_redirect_location(
|
||||||
|
session.req_header(),
|
||||||
|
self.redirect_https.port,
|
||||||
|
)
|
||||||
|
.ok_or_else(|| Error::explain(InternalError, "failed to build https redirect location"))?;
|
||||||
|
|
||||||
|
let mut header = ResponseHeader::build(self.redirect_https.code, Some(0))?;
|
||||||
|
header.insert_header("Location", &location)?;
|
||||||
|
session.set_keepalive(None);
|
||||||
|
session.write_response_header(Box::new(header), true).await?;
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(false) // false to continue processing
|
Ok(false) // false to continue processing
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,7 +261,12 @@ impl ProxyHttp for TrauditProxy {
|
|||||||
_session: &mut Session,
|
_session: &mut Session,
|
||||||
_ctx: &mut Self::CTX,
|
_ctx: &mut Self::CTX,
|
||||||
) -> Result<Box<HttpPeer>> {
|
) -> Result<Box<HttpPeer>> {
|
||||||
let addr = &self.service_config.forward_to;
|
let addr = self.service_config.forward_to.as_deref().ok_or_else(|| {
|
||||||
|
Error::explain(
|
||||||
|
InternalError,
|
||||||
|
format!("service '{}' missing forward_to", self.service_config.name),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
let peer = Box::new(HttpPeer::new(addr, false, "".to_string()));
|
let peer = Box::new(HttpPeer::new(addr, false, "".to_string()));
|
||||||
Ok(peer)
|
Ok(peer)
|
||||||
}
|
}
|
||||||
@@ -307,3 +328,85 @@ impl ProxyHttp for TrauditProxy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_https_redirect_location(req: &pingora::http::RequestHeader, target_port: u16) -> Option<String> {
|
||||||
|
let host_raw = req
|
||||||
|
.uri
|
||||||
|
.host()
|
||||||
|
.map(ToString::to_string)
|
||||||
|
.or_else(|| {
|
||||||
|
req
|
||||||
|
.headers
|
||||||
|
.get("host")
|
||||||
|
.and_then(|v| v.to_str().ok())
|
||||||
|
.map(ToString::to_string)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let authority = host_raw
|
||||||
|
.parse::<http::uri::Authority>()
|
||||||
|
.ok()
|
||||||
|
.map(|a| a.host().to_string())
|
||||||
|
.unwrap_or_else(|| host_raw.clone());
|
||||||
|
|
||||||
|
let needs_brackets = authority.contains(':') && !authority.starts_with('[');
|
||||||
|
let host = if needs_brackets {
|
||||||
|
format!("[{}]", authority)
|
||||||
|
} else {
|
||||||
|
authority
|
||||||
|
};
|
||||||
|
|
||||||
|
let host_port = if target_port == 443 {
|
||||||
|
host
|
||||||
|
} else {
|
||||||
|
format!("{}:{}", host, target_port)
|
||||||
|
};
|
||||||
|
|
||||||
|
let path_q = req
|
||||||
|
.uri
|
||||||
|
.path_and_query()
|
||||||
|
.map(|v| v.as_str())
|
||||||
|
.unwrap_or("/");
|
||||||
|
|
||||||
|
Some(format!("https://{}{}", host_port, path_q))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::build_https_redirect_location;
|
||||||
|
use pingora::http::RequestHeader;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_redirect_location_from_host_header_default_port() {
|
||||||
|
let mut req = RequestHeader::build("GET", b"/a/b?x=1", None).unwrap();
|
||||||
|
req.insert_header("Host", "example.com").unwrap();
|
||||||
|
|
||||||
|
let location = build_https_redirect_location(&req, 443).unwrap();
|
||||||
|
assert_eq!(location, "https://example.com/a/b?x=1");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_redirect_location_overrides_host_port() {
|
||||||
|
let mut req = RequestHeader::build("GET", b"/", None).unwrap();
|
||||||
|
req.insert_header("Host", "example.com:8080").unwrap();
|
||||||
|
|
||||||
|
let location = build_https_redirect_location(&req, 8443).unwrap();
|
||||||
|
assert_eq!(location, "https://example.com:8443/");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_redirect_location_ipv6_host() {
|
||||||
|
let mut req = RequestHeader::build("GET", b"/hello", None).unwrap();
|
||||||
|
req.insert_header("Host", "[2001:db8::1]:8080").unwrap();
|
||||||
|
|
||||||
|
let location = build_https_redirect_location(&req, 443).unwrap();
|
||||||
|
assert_eq!(location, "https://[2001:db8::1]/hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_redirect_location_missing_host() {
|
||||||
|
let req = RequestHeader::build("GET", b"/", None).unwrap();
|
||||||
|
|
||||||
|
let location = build_https_redirect_location(&req, 443);
|
||||||
|
assert!(location.is_none());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -126,7 +126,13 @@ pub async fn handle_connection(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 3. Connect Upstream
|
// 3. Connect Upstream
|
||||||
let mut upstream = UpstreamStream::connect(&service.forward_to).await?;
|
let forward_to = service.forward_to.as_deref().ok_or_else(|| {
|
||||||
|
io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
format!("service '{}' missing forward_to", service.name),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let mut upstream = UpstreamStream::connect(forward_to).await?;
|
||||||
|
|
||||||
// [NEW] Send Proxy Protocol Header if configured
|
// [NEW] Send Proxy Protocol Header if configured
|
||||||
if let Some(upstream_ver) = &service.upstream_proxy {
|
if let Some(upstream_ver) = &service.upstream_proxy {
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ pub async fn run(
|
|||||||
let proxy_proto_config = bind.proxy.clone();
|
let proxy_proto_config = bind.proxy.clone();
|
||||||
let mode = bind.mode;
|
let mode = bind.mode;
|
||||||
let real_ip_config = bind.real_ip.clone();
|
let real_ip_config = bind.real_ip.clone();
|
||||||
|
let redirect_https = bind.redirect_https.clone();
|
||||||
|
|
||||||
let is_tcp_service = service.service_type == "tcp";
|
let is_tcp_service = service.service_type == "tcp";
|
||||||
|
|
||||||
@@ -146,13 +147,15 @@ pub async fn run(
|
|||||||
|
|
||||||
if is_tcp_service {
|
if is_tcp_service {
|
||||||
// --- TCP Handler (with startup check) ---
|
// --- TCP Handler (with startup check) ---
|
||||||
if let Err(e) = UpstreamStream::connect(&service_config.forward_to).await {
|
if let Some(forward_to) = service_config.forward_to.as_deref() {
|
||||||
tracing::warn!(
|
if let Err(e) = UpstreamStream::connect(forward_to).await {
|
||||||
"[{}] -> '{}': startup check failed: {}",
|
tracing::warn!(
|
||||||
service_config.name,
|
"[{}] -> '{}': startup check failed: {}",
|
||||||
service_config.forward_to,
|
service_config.name,
|
||||||
e
|
forward_to,
|
||||||
);
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let db = db.clone();
|
let db = db.clone();
|
||||||
@@ -198,6 +201,7 @@ pub async fn run(
|
|||||||
listen_addr: bind_addr.clone(),
|
listen_addr: bind_addr.clone(),
|
||||||
real_ip: real_ip_config.clone(),
|
real_ip: real_ip_config.clone(),
|
||||||
add_xff_header: bind.add_xff_header,
|
add_xff_header: bind.add_xff_header,
|
||||||
|
redirect_https,
|
||||||
};
|
};
|
||||||
let mut service_obj = http_proxy_service(&conf, inner_proxy);
|
let mut service_obj = http_proxy_service(&conf, inner_proxy);
|
||||||
let app = unsafe {
|
let app = unsafe {
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ use tokio::sync::OnceCell;
|
|||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
|
|
||||||
use traudit::config::{
|
use traudit::config::{
|
||||||
BindEntry, Config, DatabaseConfig, RealIpConfig, RealIpSource, ServiceConfig, TlsConfig,
|
BindEntry, Config, DatabaseConfig, RealIpConfig, RealIpSource, RedirectHttpsConfig,
|
||||||
|
ServiceConfig, TlsConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
static INIT: Once = Once::new();
|
static INIT: Once = Once::new();
|
||||||
@@ -354,7 +355,7 @@ pub async fn prepare_env(
|
|||||||
services: vec![ServiceConfig {
|
services: vec![ServiceConfig {
|
||||||
name: "test-svc".to_string(),
|
name: "test-svc".to_string(),
|
||||||
service_type: service_type.to_string(),
|
service_type: service_type.to_string(),
|
||||||
forward_to: upstream_addr.to_string(),
|
forward_to: Some(upstream_addr.to_string()),
|
||||||
upstream_proxy: None,
|
upstream_proxy: None,
|
||||||
binds: vec![BindEntry {
|
binds: vec![BindEntry {
|
||||||
addr: bind_addr.clone(),
|
addr: bind_addr.clone(),
|
||||||
@@ -363,6 +364,7 @@ pub async fn prepare_env(
|
|||||||
tls: tls_config,
|
tls: tls_config,
|
||||||
real_ip,
|
real_ip,
|
||||||
add_xff_header: add_xff,
|
add_xff_header: add_xff,
|
||||||
|
redirect_https: RedirectHttpsConfig::default(),
|
||||||
}],
|
}],
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ use std::time::Duration;
|
|||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
|
||||||
use traudit::config::{
|
use traudit::config::{
|
||||||
BindEntry, Config, DatabaseConfig, RealIpConfig, RealIpSource, ServiceConfig,
|
BindEntry, Config, DatabaseConfig, RealIpConfig, RealIpSource, RedirectHttpsConfig,
|
||||||
|
ServiceConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
@@ -221,8 +222,9 @@ async fn prepare_chain_env() -> ChainTestResources {
|
|||||||
tls: None,
|
tls: None,
|
||||||
add_xff_header: false,
|
add_xff_header: false,
|
||||||
real_ip: None,
|
real_ip: None,
|
||||||
|
redirect_https: RedirectHttpsConfig::default(),
|
||||||
}],
|
}],
|
||||||
forward_to: addr2.clone(),
|
forward_to: Some(addr2.clone()),
|
||||||
upstream_proxy: Some("v1".to_string()),
|
upstream_proxy: Some("v1".to_string()),
|
||||||
},
|
},
|
||||||
// E2: (Proxy V1 In, Upstream Proxy V2)
|
// E2: (Proxy V1 In, Upstream Proxy V2)
|
||||||
@@ -236,8 +238,9 @@ async fn prepare_chain_env() -> ChainTestResources {
|
|||||||
tls: None,
|
tls: None,
|
||||||
add_xff_header: false,
|
add_xff_header: false,
|
||||||
real_ip: real_ip_pp.clone(),
|
real_ip: real_ip_pp.clone(),
|
||||||
|
redirect_https: RedirectHttpsConfig::default(),
|
||||||
}],
|
}],
|
||||||
forward_to: addr3.clone(),
|
forward_to: Some(addr3.clone()),
|
||||||
upstream_proxy: Some("v2".to_string()),
|
upstream_proxy: Some("v2".to_string()),
|
||||||
},
|
},
|
||||||
// E3: (Proxy V2 In, Upstream Proxy V1)
|
// E3: (Proxy V2 In, Upstream Proxy V1)
|
||||||
@@ -251,8 +254,9 @@ async fn prepare_chain_env() -> ChainTestResources {
|
|||||||
tls: None,
|
tls: None,
|
||||||
add_xff_header: false,
|
add_xff_header: false,
|
||||||
real_ip: real_ip_pp.clone(),
|
real_ip: real_ip_pp.clone(),
|
||||||
|
redirect_https: RedirectHttpsConfig::default(),
|
||||||
}],
|
}],
|
||||||
forward_to: addr4.clone(),
|
forward_to: Some(addr4.clone()),
|
||||||
upstream_proxy: Some("v1".to_string()),
|
upstream_proxy: Some("v1".to_string()),
|
||||||
},
|
},
|
||||||
// E4: (Proxy V1 In, No Upstream Proxy -> Mock Server)
|
// E4: (Proxy V1 In, No Upstream Proxy -> Mock Server)
|
||||||
@@ -266,8 +270,9 @@ async fn prepare_chain_env() -> ChainTestResources {
|
|||||||
tls: None,
|
tls: None,
|
||||||
add_xff_header: false,
|
add_xff_header: false,
|
||||||
real_ip: real_ip_pp.clone(),
|
real_ip: real_ip_pp.clone(),
|
||||||
|
redirect_https: RedirectHttpsConfig::default(),
|
||||||
}],
|
}],
|
||||||
forward_to: e4_upstream_addr.to_string(),
|
forward_to: Some(e4_upstream_addr.to_string()),
|
||||||
upstream_proxy: None,
|
upstream_proxy: None,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user