Add ability to add request headers to proxied requests

This commit is contained in:
Brian Picciano 2023-07-17 16:54:03 +02:00
parent 651f3f4bb7
commit 5099f79260
5 changed files with 67 additions and 26 deletions

View File

@ -5,7 +5,14 @@ domain:
builtins: builtins:
foo: foo:
kind: proxy kind: proxy
url: http://ok url: http://127.0.0.1:9000
request_http_headers:
- name: x-foo
value: BAR
- name: host
value: hi
- name: user-agent
value: ""
bar: bar:
kind: git kind: git
url: a url: a

View File

@ -66,13 +66,24 @@ domain:
# public: false # public: false
# An example built-in domain backed by a reverse-proxy to some other # An example built-in domain backed by a reverse-proxy to some other
# web-service. Proxies are currently limited in the following ways: # web-service. Requests to the backing service will automatically have
# X-Forwarded-For and (if HTTPS) X-Forwarded-Proto headers added to them.
#
# Proxies are currently limited in the following ways:
# * url must be to an http endpoint (not https) # * url must be to an http endpoint (not https)
# * dns.resolver_addr is ignored and the system-wide dns is used # * dns.resolver_addr is ignored and the system-wide dns is used
# #
#proxy.example.com: #proxy.example.com:
# kind: proxy # kind: proxy
# url: "http://some.other.service.com" # url: "http://some.other.service.com"
#
# # Extra headers to add to requests being proxied
# request_http_headers:
# - name: Host
# value: "yet.another.service.com"
# - name: X-HEADER-TO-DELETE
# value: ""
#
# public: false # public: false
service: service:

View File

@ -79,7 +79,9 @@ async fn main() {
} }
for (domain, builtin_domain) in &config.domain.builtins { for (domain, builtin_domain) in &config.domain.builtins {
if let domani::origin::Descr::Proxy { ref url } = builtin_domain.settings.origin_descr { if let domani::origin::Descr::Proxy { ref url, .. } =
builtin_domain.settings.origin_descr
{
if let Err(e) = domani::origin::proxy::validate_proxy_url(url) { if let Err(e) = domani::origin::proxy::validate_proxy_url(url) {
panic!("invalid config for builtin domain {domain}: {e}"); panic!("invalid config for builtin domain {domain}: {e}");
} }

View File

@ -2,6 +2,12 @@ use hex::ToHex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct DescrProxyHttpHeader {
pub name: String,
pub value: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(tag = "kind")] #[serde(tag = "kind")]
/// A unique description of an origin, from where a domain might be served. /// A unique description of an origin, from where a domain might be served.
@ -10,7 +16,10 @@ pub enum Descr {
Git { url: String, branch_name: String }, Git { url: String, branch_name: String },
#[serde(rename = "proxy")] #[serde(rename = "proxy")]
Proxy { url: String }, Proxy {
url: String,
request_http_headers: Vec<DescrProxyHttpHeader>,
},
} }
impl Descr { impl Descr {
@ -29,9 +38,17 @@ impl Descr {
h_update(url); h_update(url);
h_update(branch_name); h_update(branch_name);
} }
Descr::Proxy { url } => { Descr::Proxy {
url,
request_http_headers,
} => {
h_update("proxy"); h_update("proxy");
h_update(url); h_update(url);
for h in request_http_headers {
h_update("header");
h_update(&h.name);
h_update(&h.value);
}
} }
} }

View File

@ -1,6 +1,6 @@
use crate::error::unexpected::{self, Mappable}; use crate::error::unexpected::{self, Mappable};
use crate::{domain, origin}; use crate::{domain, origin};
use http::header::HeaderValue; use http::header::{HeaderName, HeaderValue};
use std::{net, str::FromStr}; use std::{net, str::FromStr};
// proxy is a special case because it is so tied to the underlying protocol that a request is // proxy is a special case because it is so tied to the underlying protocol that a request is
@ -30,33 +30,37 @@ pub async fn serve_http_request(
mut req: hyper::Request<hyper::Body>, mut req: hyper::Request<hyper::Body>,
req_is_https: bool, req_is_https: bool,
) -> unexpected::Result<hyper::Response<hyper::Body>> { ) -> unexpected::Result<hyper::Response<hyper::Body>> {
let proxy_url = if let origin::Descr::Proxy { ref url } = settings.origin_descr { let (proxy_url, request_http_headers) = if let origin::Descr::Proxy {
url ref url,
ref request_http_headers,
} = settings.origin_descr
{
(url, request_http_headers)
} else { } else {
panic!("non-proxy domain settings passed in: {settings:?}") panic!("non-proxy domain settings passed in: {settings:?}")
}; };
let parsed_proxy_url = for header in request_http_headers {
http::Uri::from_str(proxy_url).or_unexpected_while("parsing proxy url {proxy_url}")?; let name: HeaderName = header
.name
.as_str()
.try_into()
.map_unexpected_while(|| format!("parsing header name {}", &header.name))?;
// figure out what the host header should be, based on the host[:port] of the proxy_url if header.value == "" {
let host = { req.headers_mut().remove(name);
let authority = parsed_proxy_url.authority().or_unexpected_while(format!( continue;
"getting host from proxy url {proxy_url}, there is no host" }
))?;
let host_and_port; let value = HeaderValue::from_str(&header.value).map_unexpected_while(|| {
let mut host = authority.host(); format!(
"parsing {} as value for header {}",
&header.value, &header.name
)
})?;
if let Some(port) = authority.port() { req.headers_mut().insert(name, value);
host_and_port = format!("{host}:{port}"); }
host = host_and_port.as_str();
};
HeaderValue::from_str(host).or_unexpected()?
};
req.headers_mut().insert("host", host);
if req_is_https { if req_is_https {
req.headers_mut() req.headers_mut()