mirror of https://github.com/rwf2/Rocket.git
Fixup docs for 'proxy_proto_header'.
This commit is contained in:
parent
5c85ea3db5
commit
e9b568d9b2
|
@ -2,15 +2,30 @@ use std::fmt;
|
|||
|
||||
use uncased::{UncasedStr, AsUncased};
|
||||
|
||||
/// A protocol used to identify a specific protocol forwarded by an HTTP proxy.
|
||||
/// Value are case-insensitive.
|
||||
/// Parsed [`Config::proxy_proto_header`] value: identifies a forwarded HTTP
|
||||
/// protocol (aka [X-Forwarded-Proto]).
|
||||
///
|
||||
/// The value of the header with name [`Config::proxy_proto_header`] is parsed
|
||||
/// case-insensitively into this `enum`. For a given request, the parsed value,
|
||||
/// if the header was present, can be retrieved via [`Request::proxy_proto()`]
|
||||
/// or directly as a [request guard]. That value is used to determine whether a
|
||||
/// request's context is likely secure ([`Request::context_is_likely_secure()`])
|
||||
/// which in-turn is used to determine whether the `Secure` cookie flag is set
|
||||
/// by default when [cookies are added] to a `CookieJar`.
|
||||
///
|
||||
/// [X-Forwarded-Proto]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto
|
||||
/// [`Config::proxy_proto_header`]: ../../rocket/struct.Config.html#structfield.proxy_proto_header
|
||||
/// [`Request::proxy_proto()`]: ../../rocket/request/struct.Request.html#method.proxy_proto
|
||||
/// [`Request::context_is_likely_secure()`]: ../../rocket/request/struct.Request.html#method.context_is_likely_secure
|
||||
/// [cookies are added]: ../..//rocket/http/struct.CookieJar.html#method.add
|
||||
/// [request guard]: ../../rocket/request/trait.FromRequest.html#provided-implementations
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ProxyProto<'a> {
|
||||
/// `http` value, Hypertext Transfer Protocol.
|
||||
/// `"http"`: Hypertext Transfer Protocol.
|
||||
Http,
|
||||
/// `https` value, Hypertext Transfer Protocol Secure.
|
||||
/// `"https"`: Hypertext Transfer Protocol Secure.
|
||||
Https,
|
||||
/// Any protocol name other than `http` or `https`.
|
||||
/// Any protocol name other than `"http"` or `"https"`.
|
||||
Unknown(&'a UncasedStr),
|
||||
}
|
||||
|
||||
|
|
|
@ -93,18 +93,24 @@ pub struct Config {
|
|||
#[serde(deserialize_with = "crate::config::http_header::deserialize")]
|
||||
pub ip_header: Option<Uncased<'static>>,
|
||||
/// The name of a header, whose value is typically set by an intermediary
|
||||
/// server or proxy, which contains the protocol (HTTP or HTTPS) used by the
|
||||
/// connecting client. This should probably be [`X-Forwarded-Proto`], as
|
||||
/// that is the de facto standard. Used by [`Request::forwarded_proto()`]
|
||||
/// to determine the forwarded protocol and [`Request::forwarded_secure()`]
|
||||
/// to determine whether a request is handled in a secure context.
|
||||
/// server or proxy, which contains the protocol ("http" or "https") used by
|
||||
/// the connecting client. This is usually [`"X-Forwarded-Proto"`], as that
|
||||
/// is the de-facto standard.
|
||||
///
|
||||
/// [`X-Forwarded-Proto`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto
|
||||
/// The header value is parsed into a [`ProxyProto`], accessible via
|
||||
/// [`Request::proxy_proto()`]. The value influences
|
||||
/// [`Request::context_is_likely_secure()`] and the default value for the
|
||||
/// `Secure` flag in cookies added to [`CookieJar`]s.
|
||||
///
|
||||
/// To disable using any header for this purpose, set this value to `false`
|
||||
/// or `None`. Deserialization semantics are identical to those of [`ip_header`].
|
||||
/// or `None`. Deserialization semantics are identical to those of
|
||||
/// [`Config::ip_header`] (the value must be a valid HTTP header name).
|
||||
///
|
||||
/// **(default: `None`)**
|
||||
///
|
||||
/// [`CookieJar`]: crate::http::CookieJar
|
||||
/// [`ProxyProto`]: crate::http::ProxyProto
|
||||
/// [`"X-Forwarded-Proto"`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto
|
||||
#[serde(deserialize_with = "crate::config::http_header::deserialize")]
|
||||
pub proxy_proto_header: Option<Uncased<'static>>,
|
||||
/// Streaming read size limits. **(default: [`Limits::default()`])**
|
||||
|
|
|
@ -278,12 +278,13 @@ impl<'a> CookieJar<'a> {
|
|||
///
|
||||
/// * `path`: `"/"`
|
||||
/// * `SameSite`: `Strict`
|
||||
/// * `Secure`: `true` if [`Request::context_is_likely_secure()`]
|
||||
///
|
||||
/// Furthermore, if TLS is enabled or handled by a proxy (as determined by
|
||||
/// [`Request::context_is_likely_secure()`]), the `Secure` cookie flag is set.
|
||||
/// These defaults ensure maximum usability and security. For additional
|
||||
/// security, you may wish to set the `secure` flag explicitly.
|
||||
///
|
||||
/// [`Request::context_is_likely_secure()`]: crate::Request::context_is_likely_secure()
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
|
@ -321,11 +322,13 @@ impl<'a> CookieJar<'a> {
|
|||
/// * `SameSite`: `Strict`
|
||||
/// * `HttpOnly`: `true`
|
||||
/// * `Expires`: 1 week from now
|
||||
/// * `Secure`: `true` if [`Request::context_is_likely_secure()`]
|
||||
///
|
||||
/// Furthermore, if TLS is enabled or handled by a proxy (as determined by
|
||||
/// [`Request::context_is_likely_secure()`]), the `Secure` cookie flag is set.
|
||||
/// These defaults ensure maximum usability and security. For additional
|
||||
/// security, you may wish to set the `secure` flag explicitly.
|
||||
/// security, you may wish to set the `secure` flag explicitly and
|
||||
/// unconditionally.
|
||||
///
|
||||
/// [`Request::context_is_likely_secure()`]: crate::Request::context_is_likely_secure()
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
|
@ -510,9 +513,7 @@ impl<'a> CookieJar<'a> {
|
|||
///
|
||||
/// * `path`: `"/"`
|
||||
/// * `SameSite`: `Strict`
|
||||
///
|
||||
/// Furthermore, if TLS is enabled or handled by a proxy (as determined by
|
||||
/// [`Request::context_is_likely_secure()`]), the `Secure` cookie flag is set.
|
||||
/// * `Secure`: `true` if `Request::context_is_likely_secure()`
|
||||
fn set_defaults(&self, cookie: &mut Cookie<'static>) {
|
||||
if cookie.path().is_none() {
|
||||
cookie.set_path("/");
|
||||
|
@ -550,9 +551,7 @@ impl<'a> CookieJar<'a> {
|
|||
/// * `SameSite`: `Strict`
|
||||
/// * `HttpOnly`: `true`
|
||||
/// * `Expires`: 1 week from now
|
||||
///
|
||||
/// Furthermore, if TLS is enabled or handled by a proxy (as determined by
|
||||
/// [`Request::context_is_likely_secure()`]), the `Secure` cookie flag is set.
|
||||
/// * `Secure`: `true` if `Request::context_is_likely_secure()`
|
||||
#[cfg(feature = "secrets")]
|
||||
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
||||
fn set_private_defaults(&self, cookie: &mut Cookie<'static>) {
|
||||
|
|
|
@ -87,9 +87,10 @@ impl<'c> LocalResponse<'c> {
|
|||
let request: &'c Request<'c> = unsafe { &*(&*boxed_req as *const _) };
|
||||
|
||||
async move {
|
||||
// NOTE: The new `secure` cookie jar state will not reflect the last
|
||||
// known value in `request.cookies()`. This is okay as new cookies
|
||||
// should never be added to the resulting jar.
|
||||
// NOTE: The cookie jar `secure` state will not reflect the last
|
||||
// known value in `request.cookies()`. This is okay: new cookies
|
||||
// should never be added to the resulting jar which is the only time
|
||||
// the value is used to set cookie defaults.
|
||||
let response: Response<'c> = f(request).await;
|
||||
let mut cookies = CookieJar::new(None, request.rocket());
|
||||
for cookie in response.cookies() {
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::fmt::Debug;
|
|||
use std::net::{IpAddr, SocketAddr};
|
||||
|
||||
use crate::{Request, Route};
|
||||
use crate::outcome::{self, Outcome::*};
|
||||
use crate::outcome::{self, IntoOutcome, Outcome::*};
|
||||
|
||||
use crate::http::uri::{Host, Origin};
|
||||
use crate::http::{Status, ContentType, Accept, Method, ProxyProto, CookieJar};
|
||||
|
@ -163,8 +163,8 @@ pub type Outcome<S, E> = outcome::Outcome<S, (Status, E), Status>;
|
|||
/// * **ProxyProto**
|
||||
///
|
||||
/// Extracts the protocol of the incoming request as a [`ProxyProto`] via
|
||||
/// [`Request::proxy_proto()`] (HTTP or HTTPS). If value of the header is
|
||||
/// not known, the request is forwarded with a 404 Not Found status.
|
||||
/// [`Request::proxy_proto()`]. If no such header is present, the request is
|
||||
/// forwarded with a 500 Internal Server Error status.
|
||||
///
|
||||
/// * **SocketAddr**
|
||||
///
|
||||
|
@ -481,10 +481,7 @@ impl<'r> FromRequest<'r> for ProxyProto<'r> {
|
|||
type Error = std::convert::Infallible;
|
||||
|
||||
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||
match request.proxy_proto() {
|
||||
Some(proto) => Success(proto),
|
||||
None => Forward(Status::InternalServerError),
|
||||
}
|
||||
request.proxy_proto().or_forward(Status::InternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -389,8 +389,9 @@ impl<'r> Request<'r> {
|
|||
///
|
||||
/// The value is determined by inspecting the header named
|
||||
/// [`proxy_proto_header`](crate::Config::proxy_proto_header), if
|
||||
/// configured. If parameter isn't configured or the request doesn't contain
|
||||
/// a header named as indicated, this method returns `None`.
|
||||
/// configured, and parsing it case-insensitivity. If the parameter isn't
|
||||
/// configured or the request doesn't contain a header named as indicated,
|
||||
/// this method returns `None`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
|
@ -413,7 +414,7 @@ impl<'r> Request<'r> {
|
|||
/// assert_eq!(req.proxy_proto(), Some(ProxyProto::Https));
|
||||
///
|
||||
/// # let req = c.get("/");
|
||||
/// let req = req.header(Header::new("x-forwarded-proto", "http"));
|
||||
/// let req = req.header(Header::new("x-forwarded-proto", "HTTP"));
|
||||
/// assert_eq!(req.proxy_proto(), Some(ProxyProto::Http));
|
||||
///
|
||||
/// # let req = c.get("/");
|
||||
|
@ -438,7 +439,7 @@ impl<'r> Request<'r> {
|
|||
/// be in a secure context. We say _likely_ because it is entirely possible
|
||||
/// for the header to indicate that the connection is being proxied via
|
||||
/// HTTPS while reality differs. As such, this value should not be trusted
|
||||
/// when security is a concern.
|
||||
/// when 100% confidence is a necessity.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
|
|
|
@ -1,22 +1,13 @@
|
|||
use rocket::http::ProxyProto;
|
||||
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
#[macro_use] extern crate rocket;
|
||||
|
||||
#[get("/")]
|
||||
fn inspect_proto(proto: Option<ProxyProto>) -> String {
|
||||
proto
|
||||
.map(|proto| match proto {
|
||||
ProxyProto::Http => "http".to_owned(),
|
||||
ProxyProto::Https => "https".to_owned(),
|
||||
ProxyProto::Unknown(s) => s.to_string(),
|
||||
})
|
||||
.unwrap_or("<none>".to_owned())
|
||||
fn inspect_proto(proto: rocket::http::ProxyProto) -> String {
|
||||
proto.to_string()
|
||||
}
|
||||
|
||||
mod tests {
|
||||
use rocket::{Rocket, Build, Route};
|
||||
use rocket::http::Header;
|
||||
use rocket::http::{Header, Status};
|
||||
use rocket::local::blocking::Client;
|
||||
use rocket::figment::Figment;
|
||||
|
||||
|
@ -32,8 +23,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn check_proxy_proto_header_works() {
|
||||
let rocket = rocket_with_proto_header(Some("X-Url-Scheme"));
|
||||
let client = Client::debug(rocket).unwrap();
|
||||
let client = Client::debug(rocket_with_proto_header(Some("X-Url-Scheme"))).unwrap();
|
||||
let response = client.get("/")
|
||||
.header(Header::new("X-Forwarded-Proto", "https"))
|
||||
.header(Header::new("X-Url-Scheme", "http"))
|
||||
|
@ -41,22 +31,18 @@ mod tests {
|
|||
|
||||
assert_eq!(response.into_string().unwrap(), "http");
|
||||
|
||||
let response = client.get("/")
|
||||
.header(Header::new("X-Url-Scheme", "https"))
|
||||
.dispatch();
|
||||
|
||||
let response = client.get("/").header(Header::new("X-Url-Scheme", "https")).dispatch();
|
||||
assert_eq!(response.into_string().unwrap(), "https");
|
||||
|
||||
let response = client.get("/").dispatch();
|
||||
assert_eq!(response.into_string().unwrap(), "<none>");
|
||||
assert_eq!(response.status(), Status::InternalServerError);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_proxy_proto_header_works_again() {
|
||||
let client = Client::debug(rocket_with_proto_header(Some("x-url-scheme"))).unwrap();
|
||||
let response = client
|
||||
.get("/")
|
||||
.header(Header::new("X-Url-Scheme", "https"))
|
||||
let response = client.get("/")
|
||||
.header(Header::new("X-Url-Scheme", "hTTpS"))
|
||||
.dispatch();
|
||||
|
||||
assert_eq!(response.into_string().unwrap(), "https");
|
||||
|
@ -65,9 +51,8 @@ mod tests {
|
|||
.merge(("proxy_proto_header", "x-url-scheme"));
|
||||
|
||||
let client = Client::debug(rocket::custom(config).mount("/", routes())).unwrap();
|
||||
let response = client
|
||||
.get("/")
|
||||
.header(Header::new("X-url-Scheme", "https"))
|
||||
let response = client.get("/")
|
||||
.header(Header::new("X-url-Scheme", "HTTPS"))
|
||||
.dispatch();
|
||||
|
||||
assert_eq!(response.into_string().unwrap(), "https");
|
||||
|
@ -76,12 +61,11 @@ mod tests {
|
|||
#[test]
|
||||
fn check_default_proxy_proto_header_works() {
|
||||
let client = Client::debug_with(routes()).unwrap();
|
||||
let response = client
|
||||
.get("/")
|
||||
let response = client.get("/")
|
||||
.header(Header::new("X-Forwarded-Proto", "https"))
|
||||
.dispatch();
|
||||
|
||||
assert_eq!(response.into_string(), Some("<none>".into()));
|
||||
assert_eq!(response.status(), Status::InternalServerError);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -91,25 +75,23 @@ mod tests {
|
|||
.header(Header::new("X-Forwarded-Proto", "https"))
|
||||
.dispatch();
|
||||
|
||||
assert_eq!(response.into_string(), Some("<none>".into()));
|
||||
assert_eq!(response.status(), Status::InternalServerError);
|
||||
|
||||
let config =
|
||||
Figment::from(rocket::Config::debug_default()).merge(("proxy_proto_header", false));
|
||||
|
||||
let client = Client::debug(rocket::custom(config).mount("/", routes())).unwrap();
|
||||
let response = client
|
||||
.get("/")
|
||||
let response = client.get("/")
|
||||
.header(Header::new("X-Forwarded-Proto", "https"))
|
||||
.dispatch();
|
||||
|
||||
assert_eq!(response.into_string(), Some("<none>".into()));
|
||||
assert_eq!(response.status(), Status::InternalServerError);
|
||||
|
||||
let config = Figment::from(rocket::Config::debug_default())
|
||||
.merge(("proxy_proto_header", "x-forwarded-proto"));
|
||||
|
||||
let client = Client::debug(rocket::custom(config).mount("/", routes())).unwrap();
|
||||
let response = client
|
||||
.get("/")
|
||||
let response = client.get("/")
|
||||
.header(Header::new("x-Forwarded-Proto", "https"))
|
||||
.dispatch();
|
||||
|
||||
|
|
|
@ -152,8 +152,8 @@ workers = 16
|
|||
max_blocking = 512
|
||||
keep_alive = 5
|
||||
ident = "Rocket"
|
||||
ip_header = "X-Real-IP" # set to `false` or `None` to disable
|
||||
proxy_proto_header = `false` # set to `false` or `None` to disable
|
||||
ip_header = "X-Real-IP" # set to `false` (the default) to disable
|
||||
proxy_proto_header = `false` # set to `false` (the default) to disable
|
||||
log_level = "normal"
|
||||
temp_dir = "/tmp"
|
||||
cli_colors = true
|
||||
|
@ -365,22 +365,30 @@ mutual TLS.
|
|||
|
||||
### Proxied TLS
|
||||
|
||||
If Rocket is running behind a reverse proxy that terminates TLS, it is useful to
|
||||
know whether the original connection was made securely. Therefore, Rocket offers
|
||||
the option to configure a `proxy_proto_header` that is used to determine if the
|
||||
request is handled in a secure context. The outcome is available via
|
||||
[`Request::context_is_likely_secure()`] and used to set cookies' secure flag by
|
||||
default. To enable this behaviour, configure the header as set by your reverse
|
||||
proxy. For example:
|
||||
The `proxy_proto_header` configuration parameter allows Rocket applications to
|
||||
determine when and if a client's initial connection was likely made in a secure
|
||||
context by examining the header with the configured name. The header's value is
|
||||
parsed into a [`ProxyProto`], retrievable via [`Request::proxy_proto()`].
|
||||
|
||||
That value is in-turn inspected to determine if the initial connection was
|
||||
secure (i.e, made over TLS) and the outcome made available via
|
||||
[`Request::context_is_likely_secure()`]. The value returned by this method
|
||||
influences cookie defaults. In particular, if the method returns `true` (i.e,
|
||||
the request context is likely secure), the `Secure` cookie flag is set by
|
||||
default when a cookie is added to a [`CookieJar`].
|
||||
|
||||
To enable this behaviour, configure the header as set by your reverse proxy or
|
||||
forwarding entity. For example, to set the header name to `X-Forwarded-Proto`
|
||||
via a TOML file:
|
||||
|
||||
```toml,ignore
|
||||
proxy_proto_header = 'X-Forwarded-Proto'
|
||||
proxy_proto_header = "X-Forwarded-Proto"
|
||||
```
|
||||
|
||||
Note that this only sets the cookies' secure flag when not configured
|
||||
explicitly. This setting also provides the [request guard `ProxyProto`].
|
||||
|
||||
[`ProxyProto`]: @api/rocket/request/trait.FromRequest.html#impl-FromRequest%3C'r%3E-for-%26ProxyProto
|
||||
[`Request::proxy_proto()`]: @api/rocket/request/struct.Request.html#method.proxy_proto
|
||||
[`ProxyProto`]: @api/rocket/http/enum.ProxyProto.html
|
||||
[`CookieJar`]: @api/rocket/http/struct.CookieJar.html
|
||||
[`Request::context_is_likely_secure()`]: @api/rocket/request/struct.Request.html#method.context_is_likely_secure
|
||||
|
||||
### Workers
|
||||
|
||||
|
|
Loading…
Reference in New Issue