2

I'm trying add HTTPS enforcement to my Warp-based web app on GKE.

The GKE platform is mostly irrelevant; the cromulent detail is that the load balancer terminates SSL/TLS connections, so the “real” scheme is provided in the X-Forwarded-Proto header. The literal scheme parsed by Warp will always be HTTP.

The logic goes as follows:

  1. If the scheme is HTTPS, process requests normally.
  2. If the scheme is HTTP, send a 301 redirect to the equivalent HTTPS URL.
  3. If the scheme is anything else, send a 421 (misdirected request) error.
  4. If the X-Forwarded-Proto header is missing (or any other realistically impossible scenario occurs), send a 400 (bad request) error.

The error responses have no body content in this example, and all HTTPS requests should respond with the text Hello, world!.

The problem:

error[E0277]: the trait bound `std::result::Result<(), warp::reject::Rejection>: core::future::future::Future` is not satisfied
  --> src/main.rs:23:10
   |
23 |         .and_then(|scheme_header: Option<String>, host: String, path: FullPath| {
   |          ^^^^^^^^ the trait `core::future::future::Future` is not implemented for `std::result::Result<(), warp::reject::Rejection>`
   |
   = note: required because of the requirements on the impl of `futures_core::future::TryFuture` for `std::result::Result<(), warp::reject::Rejection>`

error[E0599]: no method named `and` found for type `warp::filter::and_then::AndThen<warp::filter::and::And<warp::filter::and::And<impl warp::filter::Filter+std::marker::Copy, impl warp::filter::Filter+std::marker::Copy>, impl warp::filter::Filter+std::marker::Copy>, [closure@src/main.rs:23:19: 43:10]>` in the current scope
  --> src/main.rs:44:10
   |
44 |         .and(filter)
   |          ^^^ method not found in `warp::filter::and_then::AndThen<warp::filter::and::And<warp::filter::and::And<impl warp::filter::Filter+std::marker::Copy, impl warp::filter::Filter+std::marker::Copy>, impl warp::filter::Filter+std::marker::Copy>, [closure@src/main.rs:23:19: 43:10]>`
   |
   = note: the method `and` exists but the following trait bounds were not satisfied:
           `&mut warp::filter::and_then::AndThen<warp::filter::and::And<warp::filter::and::And<impl warp::filter::Filter+std::marker::Copy, impl warp::filter::Filter+std::marker::Copy>, impl warp::filter::Filter+std::marker::Copy>, [closure@src/main.rs:23:19: 43:10]> : warp::filter::Filter`
           `&warp::filter::and_then::AndThen<warp::filter::and::And<warp::filter::and::And<impl warp::filter::Filter+std::marker::Copy, impl warp::filter::Filter+std::marker::Copy>, impl warp::filter::Filter+std::marker::Copy>, [closure@src/main.rs:23:19: 43:10]> : warp::filter::Filter`
           `warp::filter::and_then::AndThen<warp::filter::and::And<warp::filter::and::And<impl warp::filter::Filter+std::marker::Copy, impl warp::filter::Filter+std::marker::Copy>, impl warp::filter::Filter+std::marker::Copy>, [closure@src/main.rs:23:19: 43:10]> : warp::filter::Filter`

Clearly I’m missing something obvious here, so I’m hoping someone can nudge me in the right direction!

use futures::{FutureExt, StreamExt};
use warp::{Filter, Rejection};
use warp::filters::path::{FullPath};
use warp::http::{StatusCode, Uri};
use warp::http::uri::{Parts, Scheme};
use warp::reply::Reply;

enum SchemeError {
    InsecureScheme(Uri),
    UnknownScheme,
    MissingScheme,
}

impl warp::reject::Reject for SchemeError {}

async fn requires_https(filter: impl Filter<Extract = (Scheme,), Error = Rejection> + Copy) -> impl Filter<Extract = (), Error = Rejection> + Copy {
    warp::header::optional("X-Forwarded-Proto")
        .and(warp::header("Host"))
        .and(warp::path::full())
        .and_then(|scheme_header: Option<String>, host: String, path: FullPath| {
            if let Some(scheme) = scheme_header {
                match scheme.to_ascii_lowercase().as_str() {
                    "https" => Ok(()),
                    "http" => {
                        let mut uri_parts = Parts::default();
                        uri_parts.scheme = Some(Scheme::HTTPS);
                        uri_parts.authority = Some(host.parse().unwrap());
                        uri_parts.path_and_query = Some(path.as_str().parse().unwrap());
                        let uri_parts = uri_parts;

                        let new_uri = Uri::from_parts(uri_parts).unwrap();
                        println!("Redirecting to secure URL: {}", new_uri);
                        Err(warp::reject::custom(SchemeError::InsecureScheme(new_uri)))
                    },
                    _ => Err(warp::reject::custom(SchemeError::UnknownScheme)),
                }
            } else {
                Err(warp::reject::custom(SchemeError::MissingScheme))
            }
        })
        .and(filter)
        .recover(|err: Rejection| {
            if let Some(scheme_error) = err.find::<SchemeError>() {
                match scheme_error {
                    SchemeError::InsecureScheme(new_uri) => Ok(warp::redirect(new_uri)),
                    SchemeError::UnknownScheme => Ok(StatusCode::MISDIRECTED_REQUEST),
                    SchemeError::MissingScheme => Ok(StatusCode::BAD_REQUEST),
                }
            } else {
                Err(err)
            }
        })
}

#[tokio::main]
async fn main() {
    let routes = requires_https(warp::any().map(|| "Hello, world!"));

    warp::serve(routes)
        .run(([0, 0, 0, 0], 8080))
        .await;
}
MTCoster
  • 5,868
  • 3
  • 28
  • 49

1 Answers1

2

I am rust newbie but ran into similar compiler error

My issue was looking at warp 0.1 docs, whilst using warp 0.2 https://docs.rs/warp/0.2.0/warp/trait.Filter.html#example-3

I needed to put async move after the closure pipes in and_then

If it's not that, could be similar to understanding error: trait `futures::future::Future` is not implemented for `()`

where std::result::Result<(), warp::reject::Rejection> indicates you are returning a unit type as left-result, which might not have future implemented for it.