Giter Site home page Giter Site logo

hyper-reverse-proxy's Introduction

hyper-reverse-proxy

License CI docs version

A simple reverse proxy, to be used with Hyper.

The implementation ensures that Hop-by-hop headers are stripped correctly in both directions, and adds the client's IP address to a comma-space-separated list of forwarding addresses in the X-Forwarded-For header.

The implementation is based on Go's httputil.ReverseProxy.

Example

Add these dependencies to your Cargo.toml file.

[dependencies]
hyper-reverse-proxy = "?"
hyper = { version = "?", features = ["full"] }
tokio = { version = "?", features = ["full"] }
lazy_static = "?"
hyper-trust-dns = { version = "?", features = [
  "rustls-http2",
  "dnssec-ring",
  "dns-over-https-rustls",
  "rustls-webpki",
  "https-only"
] }

The following example will set up a reverse proxy listening on 127.0.0.1:13900, and will proxy these calls:

  • "/target/first" will be proxied to http://127.0.0.1:13901

  • "/target/second" will be proxied to http://127.0.0.1:13902

  • All other URLs will be handled by debug_request function, that will display request information.

use hyper::server::conn::AddrStream;
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Request, Response, Server, StatusCode};
use hyper_reverse_proxy::ReverseProxy;
use hyper_trust_dns::{RustlsHttpsConnector, TrustDnsResolver};
use std::net::IpAddr;
use std::{convert::Infallible, net::SocketAddr};

lazy_static::lazy_static! {
    static ref  PROXY_CLIENT: ReverseProxy<RustlsHttpsConnector> = {
        ReverseProxy::new(
            hyper::Client::builder().build::<_, hyper::Body>(TrustDnsResolver::default().into_rustls_webpki_https_connector()),
        )
    };
}

fn debug_request(req: &Request<Body>) -> Result<Response<Body>, Infallible> {
    let body_str = format!("{:?}", req);
    Ok(Response::new(Body::from(body_str)))
}

async fn handle(client_ip: IpAddr, req: Request<Body>) -> Result<Response<Body>, Infallible> {
    if req.uri().path().starts_with("/target/first") {
        match PROXY_CLIENT.call(client_ip, "http://127.0.0.1:13901", req)
            .await
        {
            Ok(response) => {
                Ok(response)
            },
            Err(_error) => {
                Ok(Response::builder()
                .status(StatusCode::INTERNAL_SERVER_ERROR)
                .body(Body::empty())
                .unwrap())},
        }
    } else if req.uri().path().starts_with("/target/second") {
        match PROXY_CLIENT.call(client_ip, "http://127.0.0.1:13902", req)
            .await
        {
            Ok(response) => Ok(response),
            Err(_error) => Ok(Response::builder()
                .status(StatusCode::INTERNAL_SERVER_ERROR)
                .body(Body::empty())
                .unwrap()),
        }
    } else {
        debug_request(&req)
    }
}

#[tokio::main]
async fn main() {
    let bind_addr = "127.0.0.1:8000";
    let addr: SocketAddr = bind_addr.parse().expect("Could not parse ip:port.");

    let make_svc = make_service_fn(|conn: &AddrStream| {
        let remote_addr = conn.remote_addr().ip();
        async move { Ok::<_, Infallible>(service_fn(move |req| handle(remote_addr, req))) }
    });

    let server = Server::bind(&addr).serve(make_svc);

    println!("Running server on {:?}", addr);

    if let Err(e) = server.await {
        eprintln!("server error: {}", e);
    }
}

A word about Security

Handling outgoing requests can be a security nightmare. This crate does not control the client for the outgoing requests, as it needs to be supplied to the proxy call. The following chapters may give you an overview on how you can secure your client using the hyper-trust-dns crate.

You can see them being used in the example.

HTTPS

You should use a secure transport in order to know who you are talking to and so you can trust the connection. By default hyper-trust-dns enables the feature flag https-only which will panic if you supply a transport scheme which isn't https. It is a healthy default as it's not only you needing to trust the source but also everyone else seeing the content on unsecure connections.

ATTENTION: if you are running on a host with added certificates in your cert store, make sure to audit them in a interval, so neither old certificates nor malicious certificates are considered as valid by your client.

TLS 1.2

By default tls 1.2 is disabled in favor of tls 1.3, because many parts of tls 1.2 can be considered as attach friendly. As not yet all services support it tls 1.2 can be enabled via the rustls-tls-12 feature.

ATTENTION: make sure to audit the services you connect to on an interval

DNSSEC

As dns queries and entries aren't "trustworthy" by default from a security standpoint. DNSSEC adds a new cryptographic layer for verification. To enable it use the dnssec-ring feature.

HTTP/2

By default only rustlss http1 feature is enabled for dns queries. While http/3 might be just around the corner. http/2 support can be enabled using the rustls-http2 feature.

DoT & DoH

DoT and DoH provide you with a secure transport between you and your dns.

By default none of them are enabled. If you would like to enabled them, you can do so using the features doh and dot.

Recommendations:

  • If you need to monitor network activities in relation to accessed ports, use dot with the dns-over-rustls feature flag
  • If you are out in the wild and have no need to monitor based on ports, doh with the dns-over-https-rustls feature flag as it will blend in with other https traffic

It is highly recommended to use one of them.

Currently only includes dns queries as esni or ech is still in draft by the ietf

hyper-reverse-proxy's People

Contributors

ameobea avatar bamthomas avatar brendanzab avatar chesedo avatar felipenoris avatar jabdoa2 avatar somehowchris avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

hyper-reverse-proxy's Issues

cannot find trait `Connect` in module `hyper::client::connect`

hyper::client::connect::Connect seems rename to hyper::client::connect::Connected

ReverseProxy::new(hyper::Client::new())

   Compiling hyper-reverse-proxy v0.5.2-dev (https://github.com/felipenoris/hyper-reverse-proxy#102d50a0)
error[E0432]: unresolved import `hyper::Client`
   --> /home/w/.cargo/git/checkouts/hyper-reverse-proxy-d3515e9b5b577477/102d50a/src/lib.rs:118:19
    |
118 | use hyper::{Body, Client, Error, Request, Response, StatusCode};
    |                   ^^^^^^
    |                   |
    |                   no `Client` in the root
    |                   help: a similar name exists in the module (notice the capitalization): `client`

error[E0405]: cannot find trait `Connect` in module `hyper::client::connect`
   --> /home/w/.cargo/git/checkouts/hyper-reverse-proxy-d3515e9b5b577477/102d50a/src/lib.rs:389:50
    |
389 | pub async fn call<'a, T: hyper::client::connect::Connect + Clone + Send + Sync + 'static>(
    |                                                  ^^^^^^^ not found in `hyper::client::connect`

error[E0405]: cannot find trait `Connect` in module `hyper::client::connect`
   --> /home/w/.cargo/git/checkouts/hyper-reverse-proxy-d3515e9b5b577477/102d50a/src/lib.rs:455:52
    |
455 | pub struct ReverseProxy<T: hyper::client::connect::Connect + Clone + Send + Sync + 'static> {
    |                                                    ^^^^^^^ not found in `hyper::client::connect`

error[E0405]: cannot find trait `Connect` in module `hyper::client::connect`
   --> /home/w/.cargo/git/checkouts/hyper-reverse-proxy-d3515e9b5b577477/102d50a/src/lib.rs:459:33
    |
459 | impl<T: hyper::client::connect::Connect + Clone + Send + Sync + 'static> ReverseProxy<T> {
    |                                 ^^^^^^^ not found in `hyper::client::connect`

Host header should not be set by reverse proxy

HRP is currently overwriting the Host header on the proxied request in all cases (see here). By doing so it makes it impossible for the caller to set its own Host header value.

This is breaking my use-case where I have a reverse-proxy downstream of HRP listening on http://[::1]:4080. The request being made by HRP ends up having Host: [::1], which is incorrect, and there's nothing I can do about it.

As far as I can tell there's no real reason for this header to be being added. The original issue, #18, mentions that it was done to match the go implementation, but if you check the link you'll see that the go implementation is rewriting the Host in the URL, not the header. If someone needs the header to match the proxy URL's host they can set it manually on their request, but I don't believe it should be forced.

I have a branch here where I removed the line which is adding the header (commit), and will be using it for my own projects. It would be great to get this upstreamed though, as it's a fairly severe bug I'd say.

(btw: is this project maintained anymore? It seems to have stalled out)

Handle Trailers

Go's httputil.ReverseProxy handles Trailers. We currently don't. It would be a tricky thing to do because they need to be inserted after the body has been streamed. I'm not sure how to do it with hyper.

See reverseproxy.go for an example of how the go folks do it!

"Unsupported scheme http" when running the example.

Hello,

When running the example with the following dependencies :

[package]
name = "proxy_2"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
hyper-reverse-proxy = { git = "https://github.com/felipenoris/hyper-reverse-proxy", branch = "master" }
hyper = { version = "0.14.18", features = ["full"] }
tokio = { version = "1.18.2", features = ["full"] }
lazy_static = "1.4.0"
hyper-trust-dns = { version = "0.4.2", features = [
  "rustls-http2",
  "dnssec-ring",
  "dns-over-https-rustls",
  "rustls-webpki",
  "https-only"
] }

I get an ̀_error:HyperError(hyper::Error(Connect, Custom { kind: Other, error: "Unsupported scheme http" })) when proxying to a local http service. It seems to be related to hyper-trust-dns client, but removing the feature "https-only" does nothing...

cannot run test or bench on stable Rust

error[E0554]: `#![feature]` may not be used on the stable release channel
  --> src/lib.rs:97:45
   |
97 | #![cfg_attr(all(not(stable), test), feature(test))]

Does it support WebSockets

WebSocket support typically requires additional code on proxying part.

Does hyper-reverse-proxy handle HTTP Upgrade and HTTP CONNECTs properly to support those features?

Case issue when upgrading to websockets

Hello,

Thanks for this amazing crate.
I have an error when proxying ttyd : "backend tried to switch to protocol Some("WebSocket") when Some("websocket") was requested"... It would seems that converting all to lowercase like this :

fn get_upgrade_type(headers: &HeaderMap) -> Option<String> {
    if headers
        .get(&*CONNECTION_HEADER)
        .map(|value| {
            value
                .to_str()
                .unwrap()
                .split(',')
                .any(|e| e.trim().to_lowercase() == "upgrade")
        })
        .unwrap_or(false)
    {
        if let Some(upgrade_value) = headers.get(&*UPGRADE_HEADER) {
            debug!(
                "Found upgrade header with value: {}",
                upgrade_value.to_str().unwrap().to_owned()
            );

            return Some(upgrade_value.to_str().unwrap().to_lowercase().to_owned());
        }
    }

    None
}

would do the trick.

If it is OK for you I can make a PR, or let you change it directly, as convenient for you.

Best regards.

Update the docs

  • rewrite docs main page with new examples
  • static client
  • ws support
  • notes on security
  • sync README.md with main page docs

I'm maintaining a fork

Hi all, I've begun a fork of this project at the following repo: https://code.betamike.com/micropelago/hyper-reverse-proxy/

I've extended upstream with the following changes:

  • Fix for #53
  • Upgraded to hyper v1
  • Fixed some panics which can occur on a failed websocket upgrade

I'm not going to bother creating a new crates.io package for this or anything like that, if you'd like to use my fork you can do so by adding the following to your Cargo.toml:

[patch.crates-io]
hyper-reverse-proxy = { git = "https://code.betamike.com/micropelago/hyper-reverse-proxy.git", branch = "master" }

If you have any issues or patches with my fork you'd like me to be aware of please feel free to email me at the address in my GH profile.

Cheers!

worker panic on websocket forward

Jul 06 13:51:48 yaya rust-webserver[2689071]: thread 'tokio-runtime-worker' panicked at 'coping between upgraded connections failed: Kind(UnexpectedEof)', /build/cargo-vendor-dir/hyper-reverse-proxy-0.5.2-dev/src/lib.rs:425:26

Example in readme fails to compile

I get the following error, when copying the readme's example verbatim into an .rs file:

the value of the associated type `Output` (from trait `futures_util::Future`) must be specifiedrustcE0191
gql_post.rs(6, 19): specify the associated type: `Future<Item=Response<Body>, Error=hyper::Error, Output = Type>`
trait objects must include the `dyn` keywordrustcE0782
gql_post.rs(6, 19): add `dyn` keyword before this trait: `dyn `

Screenshot:

Support for hyper 0.13

I've tried using this crate with the latest version of hyper (0.13.4) but unfortunately it turns out this doesn't work out of the box. Are there any plans for updating this repo to the latest hyper version? :-)

Custom error logging

We should allow folks to log failed requests in their preferred way, be it printf, slog, or something else...

trailers are not preserved in responses

Hi there, great library!
I was reviewing this a little and found that hop-headers get removed from both requests and responses, but only requests put the trailers back in afterwards. I tried to compare this to the Go implementation, it is a bit hard to read, but it seems they also put the trailer back in.

Besides that, I saw that you have some unreleased changes on master. The newer API with the persistent client object is quite nice, do you plan to release this version?

Thank you!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.