Merge branch 'junkurihara:develop' into patch-1
This commit is contained in:
commit
7e9b7985db
14 changed files with 78 additions and 39 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
|
@ -2,6 +2,17 @@
|
||||||
|
|
||||||
## 0.10.0 (Unreleased)
|
## 0.10.0 (Unreleased)
|
||||||
|
|
||||||
|
## 0.9.1
|
||||||
|
|
||||||
|
### Important Changes
|
||||||
|
|
||||||
|
- Feat: Support `https_redirection_port` option to redirect http requests to https with custom port.
|
||||||
|
|
||||||
|
### Improvement
|
||||||
|
|
||||||
|
- Refactor: lots of minor improvements
|
||||||
|
- Deps
|
||||||
|
|
||||||
## 0.9.0
|
## 0.9.0
|
||||||
|
|
||||||
### Important Changes
|
### Important Changes
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.9.0"
|
version = "0.9.1"
|
||||||
authors = ["Jun Kurihara"]
|
authors = ["Jun Kurihara"]
|
||||||
homepage = "https://github.com/junkurihara/rust-rpxy"
|
homepage = "https://github.com/junkurihara/rust-rpxy"
|
||||||
repository = "https://github.com/junkurihara/rust-rpxy"
|
repository = "https://github.com/junkurihara/rust-rpxy"
|
||||||
|
|
|
||||||
10
README.md
10
README.md
|
|
@ -315,6 +315,16 @@ The above configuration is common to all ACME enabled domains. Note that the htt
|
||||||
|
|
||||||
## TIPS
|
## TIPS
|
||||||
|
|
||||||
|
### Set custom port for HTTPS redirection
|
||||||
|
|
||||||
|
Consider a case where `rpxy` is running on a container. Then when the container manager maps port A (e.g., 80/443) of the host to port B (e.g., 8080/8443) of the container for http and https, `rpxy` must be configured with port B for `listen_port` and `listen_port_tls`. However, when you want to set `http_redirection=true` for some backend apps, `rpxy` issues the redirection response 301 with the port B by default, which is not accessible from the outside of the container. To avoid this, you can set a custom port for the redirection response by specifying `https_redirection_port` in `config.toml`. In this case, port A should be set for `https_redirection_port`, then the redirection response 301 will be issued with the port A.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
listen_port = 8080
|
||||||
|
listen_port_tls = 8443
|
||||||
|
https_redirection_port = 443
|
||||||
|
```
|
||||||
|
|
||||||
### Using Private Key Issued by Let's Encrypt
|
### Using Private Key Issued by Let's Encrypt
|
||||||
|
|
||||||
If you obtain certificates and private keys from [Let's Encrypt](https://letsencrypt.org/), you have PKCS1-formatted private keys. So you need to convert such retrieved private keys into PKCS8 format to use in `rpxy`.
|
If you obtain certificates and private keys from [Let's Encrypt](https://letsencrypt.org/), you have PKCS1-formatted private keys. So you need to convert such retrieved private keys into PKCS8 format to use in `rpxy`.
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,11 @@
|
||||||
listen_port = 8080
|
listen_port = 8080
|
||||||
listen_port_tls = 8443
|
listen_port_tls = 8443
|
||||||
|
|
||||||
|
# Optional. If you listen on a custom port like 8443 but redirect with firewall to 443
|
||||||
|
# When you specify this, the server sends a redirection response 301 with specified port to the client for plaintext http request.
|
||||||
|
# Otherwise, the server sends 301 with the same port as `listen_port_tls`.
|
||||||
|
# https_redirection_port = 443
|
||||||
|
|
||||||
# Optional for h2 and http1.1
|
# Optional for h2 and http1.1
|
||||||
tcp_listen_backlog = 1024
|
tcp_listen_backlog = 1024
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ url = { version = "2.5.2" }
|
||||||
rustc-hash = "2.0.0"
|
rustc-hash = "2.0.0"
|
||||||
thiserror = "1.0.63"
|
thiserror = "1.0.63"
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
async-trait = "0.1.81"
|
async-trait = "0.1.82"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
aws-lc-rs = { version = "1.8.1", default-features = false, features = [
|
aws-lc-rs = { version = "1.8.1", default-features = false, features = [
|
||||||
"aws-lc-sys",
|
"aws-lc-sys",
|
||||||
|
|
@ -25,10 +25,10 @@ rustls = { version = "0.23.12", default-features = false, features = [
|
||||||
"std",
|
"std",
|
||||||
"aws_lc_rs",
|
"aws_lc_rs",
|
||||||
] }
|
] }
|
||||||
rustls-platform-verifier = { version = "0.3.3" }
|
rustls-platform-verifier = { version = "0.3.4" }
|
||||||
rustls-acme = { path = "../submodules/rustls-acme/", default-features = false, features = [
|
rustls-acme = { path = "../submodules/rustls-acme/", default-features = false, features = [
|
||||||
"aws-lc-rs",
|
"aws-lc-rs",
|
||||||
] }
|
] }
|
||||||
tokio = { version = "1.39.3", default-features = false }
|
tokio = { version = "1.40.0", default-features = false }
|
||||||
tokio-util = { version = "0.7.11", default-features = false }
|
tokio-util = { version = "0.7.12", default-features = false }
|
||||||
tokio-stream = { version = "0.1.15", default-features = false }
|
tokio-stream = { version = "0.1.16", default-features = false }
|
||||||
|
|
|
||||||
|
|
@ -31,20 +31,20 @@ rpxy-lib = { path = "../rpxy-lib/", default-features = false, features = [
|
||||||
mimalloc = { version = "*", default-features = false }
|
mimalloc = { version = "*", default-features = false }
|
||||||
anyhow = "1.0.86"
|
anyhow = "1.0.86"
|
||||||
rustc-hash = "2.0.0"
|
rustc-hash = "2.0.0"
|
||||||
serde = { version = "1.0.208", default-features = false, features = ["derive"] }
|
serde = { version = "1.0.209", default-features = false, features = ["derive"] }
|
||||||
tokio = { version = "1.39.3", default-features = false, features = [
|
tokio = { version = "1.40.0", default-features = false, features = [
|
||||||
"net",
|
"net",
|
||||||
"rt-multi-thread",
|
"rt-multi-thread",
|
||||||
"time",
|
"time",
|
||||||
"sync",
|
"sync",
|
||||||
"macros",
|
"macros",
|
||||||
] }
|
] }
|
||||||
tokio-util = { version = "0.7.11", default-features = false }
|
tokio-util = { version = "0.7.12", default-features = false }
|
||||||
async-trait = "0.1.81"
|
async-trait = "0.1.82"
|
||||||
futures-util = { version = "0.3.30", default-features = false }
|
futures-util = { version = "0.3.30", default-features = false }
|
||||||
|
|
||||||
# config
|
# config
|
||||||
clap = { version = "4.5.16", features = ["std", "cargo", "wrap_help"] }
|
clap = { version = "4.5.17", features = ["std", "cargo", "wrap_help"] }
|
||||||
toml = { version = "0.8.19", default-features = false, features = ["parse"] }
|
toml = { version = "0.8.19", default-features = false, features = ["parse"] }
|
||||||
hot_reload = "0.1.6"
|
hot_reload = "0.1.6"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,13 @@ pub fn build_settings(config: &ConfigToml) -> std::result::Result<(ProxyConfig,
|
||||||
"Some apps serves only plaintext HTTP"
|
"Some apps serves only plaintext HTTP"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// https redirection port must be configured only when both http_port and https_port are configured.
|
||||||
|
if proxy_config.https_redirection_port.is_some() {
|
||||||
|
ensure!(
|
||||||
|
proxy_config.https_port.is_some() && proxy_config.http_port.is_some(),
|
||||||
|
"https_redirection_port can be specified only when both http_port and https_port are specified"
|
||||||
|
);
|
||||||
|
}
|
||||||
// https redirection can be configured if both ports are active
|
// https redirection can be configured if both ports are active
|
||||||
if !(proxy_config.https_port.is_some() && proxy_config.http_port.is_some()) {
|
if !(proxy_config.https_port.is_some() && proxy_config.http_port.is_some()) {
|
||||||
ensure!(
|
ensure!(
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ pub struct ConfigToml {
|
||||||
pub listen_port: Option<u16>,
|
pub listen_port: Option<u16>,
|
||||||
pub listen_port_tls: Option<u16>,
|
pub listen_port_tls: Option<u16>,
|
||||||
pub listen_ipv6: Option<bool>,
|
pub listen_ipv6: Option<bool>,
|
||||||
|
pub https_redirection_port: Option<u16>,
|
||||||
pub tcp_listen_backlog: Option<u32>,
|
pub tcp_listen_backlog: Option<u32>,
|
||||||
pub max_concurrent_streams: Option<u32>,
|
pub max_concurrent_streams: Option<u32>,
|
||||||
pub max_clients: Option<u32>,
|
pub max_clients: Option<u32>,
|
||||||
|
|
@ -107,6 +108,11 @@ impl TryInto<ProxyConfig> for &ConfigToml {
|
||||||
// listen port and socket
|
// listen port and socket
|
||||||
http_port: self.listen_port,
|
http_port: self.listen_port,
|
||||||
https_port: self.listen_port_tls,
|
https_port: self.listen_port_tls,
|
||||||
|
https_redirection_port: if self.https_redirection_port.is_some() {
|
||||||
|
self.https_redirection_port
|
||||||
|
} else {
|
||||||
|
self.listen_port_tls
|
||||||
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
ensure!(
|
ensure!(
|
||||||
|
|
|
||||||
|
|
@ -17,23 +17,23 @@ http3 = []
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rustc-hash = { version = "2.0.0" }
|
rustc-hash = { version = "2.0.0" }
|
||||||
tracing = { version = "0.1.40" }
|
tracing = { version = "0.1.40" }
|
||||||
derive_builder = { version = "0.20.0" }
|
derive_builder = { version = "0.20.1" }
|
||||||
thiserror = { version = "1.0.63" }
|
thiserror = { version = "1.0.63" }
|
||||||
hot_reload = { version = "0.1.6" }
|
hot_reload = { version = "0.1.6" }
|
||||||
async-trait = { version = "0.1.81" }
|
async-trait = { version = "0.1.82" }
|
||||||
rustls = { version = "0.23.12", default-features = false, features = [
|
rustls = { version = "0.23.12", default-features = false, features = [
|
||||||
"std",
|
"std",
|
||||||
"aws_lc_rs",
|
"aws_lc_rs",
|
||||||
] }
|
] }
|
||||||
rustls-pemfile = { version = "2.1.3" }
|
rustls-pemfile = { version = "2.1.3" }
|
||||||
rustls-webpki = { version = "0.102.6", default-features = false, features = [
|
rustls-webpki = { version = "0.102.7", default-features = false, features = [
|
||||||
"std",
|
"std",
|
||||||
"aws_lc_rs",
|
"aws_lc_rs",
|
||||||
] }
|
] }
|
||||||
x509-parser = { version = "0.16.0" }
|
x509-parser = { version = "0.16.0" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { version = "1.39.3", default-features = false, features = [
|
tokio = { version = "1.40.0", default-features = false, features = [
|
||||||
"rt-multi-thread",
|
"rt-multi-thread",
|
||||||
"macros",
|
"macros",
|
||||||
] }
|
] }
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,9 @@ acme = ["dep:rpxy-acme"]
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
rustc-hash = "2.0.0"
|
rustc-hash = "2.0.0"
|
||||||
bytes = "1.7.1"
|
bytes = "1.7.1"
|
||||||
derive_builder = "0.20.0"
|
derive_builder = "0.20.1"
|
||||||
futures = { version = "0.3.30", features = ["alloc", "async-await"] }
|
futures = { version = "0.3.30", features = ["alloc", "async-await"] }
|
||||||
tokio = { version = "1.39.3", default-features = false, features = [
|
tokio = { version = "1.40.0", default-features = false, features = [
|
||||||
"net",
|
"net",
|
||||||
"rt-multi-thread",
|
"rt-multi-thread",
|
||||||
"time",
|
"time",
|
||||||
|
|
@ -43,9 +43,9 @@ tokio = { version = "1.39.3", default-features = false, features = [
|
||||||
"macros",
|
"macros",
|
||||||
"fs",
|
"fs",
|
||||||
] }
|
] }
|
||||||
tokio-util = { version = "0.7.11", default-features = false }
|
tokio-util = { version = "0.7.12", default-features = false }
|
||||||
pin-project-lite = "0.2.14"
|
pin-project-lite = "0.2.14"
|
||||||
async-trait = "0.1.81"
|
async-trait = "0.1.82"
|
||||||
|
|
||||||
# Error handling
|
# Error handling
|
||||||
anyhow = "1.0.86"
|
anyhow = "1.0.86"
|
||||||
|
|
@ -64,9 +64,7 @@ hyper-tls = { version = "0.6.0", features = [
|
||||||
"alpn",
|
"alpn",
|
||||||
"vendored",
|
"vendored",
|
||||||
], optional = true }
|
], optional = true }
|
||||||
# TODO: Work around to enable rustls-platform-verifier feature: https://github.com/rustls/hyper-rustls/pull/276
|
hyper-rustls = { version = "0.27.3", default-features = false, features = [
|
||||||
# hyper-rustls = { version = "0.27.2", default-features = false, features = [
|
|
||||||
hyper-rustls = { git = "https://github.com/junkurihara/hyper-rustls", branch = "main", features = [
|
|
||||||
"aws-lc-rs",
|
"aws-lc-rs",
|
||||||
"http1",
|
"http1",
|
||||||
"http2",
|
"http2",
|
||||||
|
|
@ -86,7 +84,7 @@ rpxy-acme = { path = "../rpxy-acme/", default-features = false, optional = true
|
||||||
tracing = { version = "0.1.40" }
|
tracing = { version = "0.1.40" }
|
||||||
|
|
||||||
# http/3
|
# http/3
|
||||||
quinn = { version = "0.11.3", optional = true }
|
quinn = { version = "0.11.5", optional = true }
|
||||||
h3 = { version = "0.0.6", features = ["tracing"], optional = true }
|
h3 = { version = "0.0.6", features = ["tracing"], optional = true }
|
||||||
h3-quinn = { version = "0.0.7", optional = true }
|
h3-quinn = { version = "0.0.7", optional = true }
|
||||||
s2n-quic-h3 = { path = "../submodules/s2n-quic-h3/", features = [
|
s2n-quic-h3 = { path = "../submodules/s2n-quic-h3/", features = [
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,12 @@ pub struct ProxyConfig {
|
||||||
pub listen_sockets: Vec<SocketAddr>,
|
pub listen_sockets: Vec<SocketAddr>,
|
||||||
/// http port
|
/// http port
|
||||||
pub http_port: Option<u16>,
|
pub http_port: Option<u16>,
|
||||||
/// https port
|
/// https port listening for TLS by default
|
||||||
pub https_port: Option<u16>,
|
pub https_port: Option<u16>,
|
||||||
|
/// https redirection port that notifies the client the port to connect to.
|
||||||
|
/// Tis is used when the reverse proxy is behind a middlebox mapping the https port A to the reverse proxy's https port B.
|
||||||
|
/// Typically, it is the container environment. (e.g. the host exposes 443 and the container exposes 8443 for https, then the redirection port is 443)
|
||||||
|
pub https_redirection_port: Option<u16>,
|
||||||
/// tcp listen backlog
|
/// tcp listen backlog
|
||||||
pub tcp_listen_backlog: u32,
|
pub tcp_listen_backlog: u32,
|
||||||
|
|
||||||
|
|
@ -85,6 +89,7 @@ impl Default for ProxyConfig {
|
||||||
listen_sockets: Vec::new(),
|
listen_sockets: Vec::new(),
|
||||||
http_port: None,
|
http_port: None,
|
||||||
https_port: None,
|
https_port: None,
|
||||||
|
https_redirection_port: None,
|
||||||
tcp_listen_backlog: TCP_LISTEN_BACKLOG,
|
tcp_listen_backlog: TCP_LISTEN_BACKLOG,
|
||||||
|
|
||||||
// TODO: Reconsider each timeout values
|
// TODO: Reconsider each timeout values
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,11 @@ where
|
||||||
"Redirect to secure connection: {}",
|
"Redirect to secure connection: {}",
|
||||||
<&ServerName as TryInto<String>>::try_into(&backend_app.server_name).unwrap_or_default()
|
<&ServerName as TryInto<String>>::try_into(&backend_app.server_name).unwrap_or_default()
|
||||||
);
|
);
|
||||||
return secure_redirection_response(&backend_app.server_name, self.globals.proxy_config.https_port, &req);
|
return secure_redirection_response(
|
||||||
|
&backend_app.server_name,
|
||||||
|
self.globals.proxy_config.https_redirection_port,
|
||||||
|
&req,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find reverse proxy for given path and choose one of upstream host
|
// Find reverse proxy for given path and choose one of upstream host
|
||||||
|
|
|
||||||
|
|
@ -30,16 +30,12 @@ pub(super) fn takeout_sticky_cookie_lb_context(
|
||||||
let cookies_iter = entry
|
let cookies_iter = entry
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|v| v.to_str().unwrap_or("").split(';').map(|v| v.trim()));
|
.flat_map(|v| v.to_str().unwrap_or("").split(';').map(|v| v.trim()));
|
||||||
let (sticky_cookies, without_sticky_cookies): (Vec<_>, Vec<_>) = cookies_iter
|
let (sticky_cookies, without_sticky_cookies): (Vec<_>, Vec<_>) =
|
||||||
.into_iter()
|
cookies_iter.into_iter().partition(|v| v.starts_with(expected_cookie_name));
|
||||||
.partition(|v| v.starts_with(expected_cookie_name));
|
|
||||||
if sticky_cookies.is_empty() {
|
if sticky_cookies.is_empty() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
ensure!(
|
ensure!(sticky_cookies.len() == 1, "Invalid cookie: Multiple sticky cookie values");
|
||||||
sticky_cookies.len() == 1,
|
|
||||||
"Invalid cookie: Multiple sticky cookie values"
|
|
||||||
);
|
|
||||||
|
|
||||||
let cookies_passed_to_upstream = without_sticky_cookies.join("; ");
|
let cookies_passed_to_upstream = without_sticky_cookies.join("; ");
|
||||||
let cookie_passed_to_lb = sticky_cookies.first().unwrap();
|
let cookie_passed_to_lb = sticky_cookies.first().unwrap();
|
||||||
|
|
@ -59,10 +55,7 @@ pub(super) fn takeout_sticky_cookie_lb_context(
|
||||||
/// Set-Cookie if LB Sticky is enabled and if cookie is newly created/updated.
|
/// Set-Cookie if LB Sticky is enabled and if cookie is newly created/updated.
|
||||||
/// Set-Cookie response header could be in multiple lines.
|
/// Set-Cookie response header could be in multiple lines.
|
||||||
/// https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Set-Cookie
|
/// https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Set-Cookie
|
||||||
pub(super) fn set_sticky_cookie_lb_context(
|
pub(super) fn set_sticky_cookie_lb_context(headers: &mut HeaderMap, context_from_lb: &LoadBalanceContext) -> Result<()> {
|
||||||
headers: &mut HeaderMap,
|
|
||||||
context_from_lb: &LoadBalanceContext,
|
|
||||||
) -> Result<()> {
|
|
||||||
let sticky_cookie_string: String = context_from_lb.sticky_cookie.clone().try_into()?;
|
let sticky_cookie_string: String = context_from_lb.sticky_cookie.clone().try_into()?;
|
||||||
let new_header_val: HeaderValue = sticky_cookie_string.parse()?;
|
let new_header_val: HeaderValue = sticky_cookie_string.parse()?;
|
||||||
let expected_cookie_name = &context_from_lb.sticky_cookie.value.name;
|
let expected_cookie_name = &context_from_lb.sticky_cookie.value.name;
|
||||||
|
|
@ -122,7 +115,7 @@ pub(super) fn apply_upstream_options_to_header(
|
||||||
// add upgrade-insecure-requests in request header if not exist
|
// add upgrade-insecure-requests in request header if not exist
|
||||||
headers
|
headers
|
||||||
.entry(header::UPGRADE_INSECURE_REQUESTS)
|
.entry(header::UPGRADE_INSECURE_REQUESTS)
|
||||||
.or_insert(HeaderValue::from_bytes(&[b'1']).unwrap());
|
.or_insert(HeaderValue::from_bytes(b"1").unwrap());
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
@ -141,7 +134,7 @@ pub(super) fn append_header_entry_with_comma(headers: &mut HeaderMap, key: &str,
|
||||||
// entry.append(value.parse::<HeaderValue>()?);
|
// entry.append(value.parse::<HeaderValue>()?);
|
||||||
let mut new_value = Vec::<u8>::with_capacity(entry.get().as_bytes().len() + 2 + value.len());
|
let mut new_value = Vec::<u8>::with_capacity(entry.get().as_bytes().len() + 2 + value.len());
|
||||||
new_value.put_slice(entry.get().as_bytes());
|
new_value.put_slice(entry.get().as_bytes());
|
||||||
new_value.put_slice(&[b',', b' ']);
|
new_value.put_slice(b", ");
|
||||||
new_value.put_slice(value.as_bytes());
|
new_value.put_slice(value.as_bytes());
|
||||||
entry.insert(HeaderValue::from_bytes(&new_value)?);
|
entry.insert(HeaderValue::from_bytes(&new_value)?);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ impl<B> InspectParseHost for Request<B> {
|
||||||
/// Inspect and extract hostname from either the request HOST header or request line
|
/// Inspect and extract hostname from either the request HOST header or request line
|
||||||
fn inspect_parse_host(&self) -> Result<Vec<u8>> {
|
fn inspect_parse_host(&self) -> Result<Vec<u8>> {
|
||||||
let drop_port = |v: &[u8]| {
|
let drop_port = |v: &[u8]| {
|
||||||
if v.starts_with(&[b'[']) {
|
if v.starts_with(b"[") {
|
||||||
// v6 address with bracket case. if port is specified, always it is in this case.
|
// v6 address with bracket case. if port is specified, always it is in this case.
|
||||||
let mut iter = v.split(|ptr| ptr == &b'[' || ptr == &b']');
|
let mut iter = v.split(|ptr| ptr == &b'[' || ptr == &b']');
|
||||||
iter.next().ok_or(anyhow!("Invalid Host header"))?; // first item is always blank
|
iter.next().ok_or(anyhow!("Invalid Host header"))?; // first item is always blank
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue