mirror of https://github.com/rwf2/Rocket.git
Support configurable 'X-Forwarded-Proto'.
Co-authored-by: Sergio Benitez <sb@sergio.bz>
This commit is contained in:
parent
5034ff0d1a
commit
5c85ea3db5
|
@ -4,10 +4,12 @@ mod media_type;
|
||||||
mod content_type;
|
mod content_type;
|
||||||
mod accept;
|
mod accept;
|
||||||
mod header;
|
mod header;
|
||||||
|
mod proxy_proto;
|
||||||
|
|
||||||
pub use self::content_type::ContentType;
|
pub use self::content_type::ContentType;
|
||||||
pub use self::accept::{Accept, QMediaType};
|
pub use self::accept::{Accept, QMediaType};
|
||||||
pub use self::media_type::MediaType;
|
pub use self::media_type::MediaType;
|
||||||
pub use self::header::{Header, HeaderMap};
|
pub use self::header::{Header, HeaderMap};
|
||||||
|
pub use self::proxy_proto::ProxyProto;
|
||||||
|
|
||||||
pub(crate) use self::media_type::Source;
|
pub(crate) use self::media_type::Source;
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use uncased::{UncasedStr, AsUncased};
|
||||||
|
|
||||||
|
/// A protocol used to identify a specific protocol forwarded by an HTTP proxy.
|
||||||
|
/// Value are case-insensitive.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum ProxyProto<'a> {
|
||||||
|
/// `http` value, Hypertext Transfer Protocol.
|
||||||
|
Http,
|
||||||
|
/// `https` value, Hypertext Transfer Protocol Secure.
|
||||||
|
Https,
|
||||||
|
/// Any protocol name other than `http` or `https`.
|
||||||
|
Unknown(&'a UncasedStr),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProxyProto<'_> {
|
||||||
|
/// Returns `true` if `self` is `ProxyProto::Https` and `false` otherwise.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use rocket::http::ProxyProto;
|
||||||
|
///
|
||||||
|
/// assert!(ProxyProto::Https.is_https());
|
||||||
|
/// assert!(!ProxyProto::Http.is_https());
|
||||||
|
/// ```
|
||||||
|
pub fn is_https(&self) -> bool {
|
||||||
|
self == &ProxyProto::Https
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a str> for ProxyProto<'a> {
|
||||||
|
fn from(value: &'a str) -> ProxyProto<'a> {
|
||||||
|
match value.as_uncased() {
|
||||||
|
v if v == "http" => ProxyProto::Http,
|
||||||
|
v if v == "https" => ProxyProto::Https,
|
||||||
|
v => ProxyProto::Unknown(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ProxyProto<'_> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(match *self {
|
||||||
|
ProxyProto::Http => "http",
|
||||||
|
ProxyProto::Https => "https",
|
||||||
|
ProxyProto::Unknown(s) => s.as_str(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -85,13 +85,28 @@ pub struct Config {
|
||||||
/// client. Used internally and by [`Request::client_ip()`] and
|
/// client. Used internally and by [`Request::client_ip()`] and
|
||||||
/// [`Request::real_ip()`].
|
/// [`Request::real_ip()`].
|
||||||
///
|
///
|
||||||
/// To disable using any header for this purpose, set this value to `false`.
|
/// To disable using any header for this purpose, set this value to `false`
|
||||||
/// Deserialization semantics are identical to those of [`Ident`] except
|
/// or `None`. Deserialization semantics are identical to those of [`Ident`]
|
||||||
/// that the value must syntactically be a valid HTTP header name.
|
/// except that the value must syntactically be a valid HTTP header name.
|
||||||
///
|
///
|
||||||
/// **(default: `"X-Real-IP"`)**
|
/// **(default: `"X-Real-IP"`)**
|
||||||
#[serde(deserialize_with = "crate::config::ip_header::deserialize")]
|
#[serde(deserialize_with = "crate::config::http_header::deserialize")]
|
||||||
pub ip_header: Option<Uncased<'static>>,
|
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.
|
||||||
|
///
|
||||||
|
/// [`X-Forwarded-Proto`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto
|
||||||
|
///
|
||||||
|
/// To disable using any header for this purpose, set this value to `false`
|
||||||
|
/// or `None`. Deserialization semantics are identical to those of [`ip_header`].
|
||||||
|
///
|
||||||
|
/// **(default: `None`)**
|
||||||
|
#[serde(deserialize_with = "crate::config::http_header::deserialize")]
|
||||||
|
pub proxy_proto_header: Option<Uncased<'static>>,
|
||||||
/// Streaming read size limits. **(default: [`Limits::default()`])**
|
/// Streaming read size limits. **(default: [`Limits::default()`])**
|
||||||
pub limits: Limits,
|
pub limits: Limits,
|
||||||
/// Directory to store temporary files in. **(default:
|
/// Directory to store temporary files in. **(default:
|
||||||
|
@ -189,6 +204,7 @@ impl Config {
|
||||||
max_blocking: 512,
|
max_blocking: 512,
|
||||||
ident: Ident::default(),
|
ident: Ident::default(),
|
||||||
ip_header: Some(Uncased::from_borrowed("X-Real-IP")),
|
ip_header: Some(Uncased::from_borrowed("X-Real-IP")),
|
||||||
|
proxy_proto_header: None,
|
||||||
limits: Limits::default(),
|
limits: Limits::default(),
|
||||||
temp_dir: std::env::temp_dir().into(),
|
temp_dir: std::env::temp_dir().into(),
|
||||||
keep_alive: 5,
|
keep_alive: 5,
|
||||||
|
@ -409,6 +425,11 @@ impl Config {
|
||||||
None => launch_meta_!("IP header: {}", "disabled".paint(VAL))
|
None => launch_meta_!("IP header: {}", "disabled".paint(VAL))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match self.proxy_proto_header.as_ref() {
|
||||||
|
Some(name) => launch_meta_!("Proxy-Proto header: {}", name.paint(VAL)),
|
||||||
|
None => launch_meta_!("Proxy-Proto header: {}", "disabled".paint(VAL))
|
||||||
|
}
|
||||||
|
|
||||||
launch_meta_!("limits: {}", (&self.limits).paint(VAL));
|
launch_meta_!("limits: {}", (&self.limits).paint(VAL));
|
||||||
launch_meta_!("temp dir: {}", self.temp_dir.relative().display().paint(VAL));
|
launch_meta_!("temp dir: {}", self.temp_dir.relative().display().paint(VAL));
|
||||||
launch_meta_!("http/2: {}", (cfg!(feature = "http2").paint(VAL)));
|
launch_meta_!("http/2: {}", (cfg!(feature = "http2").paint(VAL)));
|
||||||
|
@ -513,6 +534,9 @@ impl Config {
|
||||||
/// The stringy parameter name for setting/extracting [`Config::ip_header`].
|
/// The stringy parameter name for setting/extracting [`Config::ip_header`].
|
||||||
pub const IP_HEADER: &'static str = "ip_header";
|
pub const IP_HEADER: &'static str = "ip_header";
|
||||||
|
|
||||||
|
/// The stringy parameter name for setting/extracting [`Config::proxy_proto_header`].
|
||||||
|
pub const PROXY_PROTO_HEADER: &'static str = "proxy_proto_header";
|
||||||
|
|
||||||
/// The stringy parameter name for setting/extracting [`Config::limits`].
|
/// The stringy parameter name for setting/extracting [`Config::limits`].
|
||||||
pub const LIMITS: &'static str = "limits";
|
pub const LIMITS: &'static str = "limits";
|
||||||
|
|
||||||
|
@ -536,10 +560,9 @@ impl Config {
|
||||||
|
|
||||||
/// An array of all of the stringy parameter names.
|
/// An array of all of the stringy parameter names.
|
||||||
pub const PARAMETERS: &'static [&'static str] = &[
|
pub const PARAMETERS: &'static [&'static str] = &[
|
||||||
Self::ADDRESS, Self::PORT, Self::WORKERS, Self::MAX_BLOCKING,
|
Self::ADDRESS, Self::PORT, Self::WORKERS, Self::MAX_BLOCKING, Self::KEEP_ALIVE,
|
||||||
Self::KEEP_ALIVE, Self::IDENT, Self::IP_HEADER, Self::LIMITS, Self::TLS,
|
Self::IDENT, Self::IP_HEADER, Self::PROXY_PROTO_HEADER, Self::LIMITS, Self::TLS,
|
||||||
Self::SECRET_KEY, Self::TEMP_DIR, Self::LOG_LEVEL, Self::SHUTDOWN,
|
Self::SECRET_KEY, Self::TEMP_DIR, Self::LOG_LEVEL, Self::SHUTDOWN, Self::CLI_COLORS,
|
||||||
Self::CLI_COLORS,
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -114,8 +114,8 @@
|
||||||
mod ident;
|
mod ident;
|
||||||
mod config;
|
mod config;
|
||||||
mod shutdown;
|
mod shutdown;
|
||||||
mod ip_header;
|
|
||||||
mod cli_colors;
|
mod cli_colors;
|
||||||
|
mod http_header;
|
||||||
|
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
mod tls;
|
mod tls;
|
||||||
|
|
|
@ -2,8 +2,8 @@ use std::fmt;
|
||||||
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
|
||||||
use crate::Config;
|
|
||||||
use crate::http::private::cookie;
|
use crate::http::private::cookie;
|
||||||
|
use crate::{Rocket, Orbit};
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use self::cookie::{Cookie, SameSite, Iter};
|
pub use self::cookie::{Cookie, SameSite, Iter};
|
||||||
|
@ -154,17 +154,14 @@ pub use self::cookie::{Cookie, SameSite, Iter};
|
||||||
pub struct CookieJar<'a> {
|
pub struct CookieJar<'a> {
|
||||||
jar: cookie::CookieJar,
|
jar: cookie::CookieJar,
|
||||||
ops: Mutex<Vec<Op>>,
|
ops: Mutex<Vec<Op>>,
|
||||||
config: &'a Config,
|
pub(crate) state: CookieState<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Clone for CookieJar<'a> {
|
#[derive(Copy, Clone)]
|
||||||
fn clone(&self) -> Self {
|
pub(crate) struct CookieState<'a> {
|
||||||
CookieJar {
|
pub secure: bool,
|
||||||
jar: self.jar.clone(),
|
#[cfg_attr(not(feature = "secrets"), allow(unused))]
|
||||||
ops: Mutex::new(self.ops.lock().clone()),
|
pub config: &'a crate::Config,
|
||||||
config: self.config,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -173,22 +170,17 @@ enum Op {
|
||||||
Remove(Cookie<'static>, bool),
|
Remove(Cookie<'static>, bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Op {
|
|
||||||
fn cookie(&self) -> &Cookie<'static> {
|
|
||||||
match self {
|
|
||||||
Op::Add(c, _) | Op::Remove(c, _) => c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> CookieJar<'a> {
|
impl<'a> CookieJar<'a> {
|
||||||
#[inline(always)]
|
pub(crate) fn new(base: Option<cookie::CookieJar>, rocket: &'a Rocket<Orbit>) -> Self {
|
||||||
pub(crate) fn new(config: &'a Config) -> Self {
|
CookieJar {
|
||||||
CookieJar::from(cookie::CookieJar::new(), config)
|
jar: base.unwrap_or_default(),
|
||||||
}
|
ops: Mutex::new(Vec::new()),
|
||||||
|
state: CookieState {
|
||||||
pub(crate) fn from(jar: cookie::CookieJar, config: &'a Config) -> Self {
|
// This is updated dynamically when headers are received.
|
||||||
CookieJar { jar, config, ops: Mutex::new(Vec::new()) }
|
secure: rocket.config().tls_enabled(),
|
||||||
|
config: rocket.config(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the _original_ `Cookie` inside this container
|
/// Returns a reference to the _original_ `Cookie` inside this container
|
||||||
|
@ -236,7 +228,7 @@ impl<'a> CookieJar<'a> {
|
||||||
#[cfg(feature = "secrets")]
|
#[cfg(feature = "secrets")]
|
||||||
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
||||||
pub fn get_private(&self, name: &str) -> Option<Cookie<'static>> {
|
pub fn get_private(&self, name: &str) -> Option<Cookie<'static>> {
|
||||||
self.jar.private(&self.config.secret_key.key).get(name)
|
self.jar.private(&self.state.config.secret_key.key).get(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the _original or pending_ `Cookie` inside this
|
/// Returns a reference to the _original or pending_ `Cookie` inside this
|
||||||
|
@ -287,7 +279,10 @@ impl<'a> CookieJar<'a> {
|
||||||
/// * `path`: `"/"`
|
/// * `path`: `"/"`
|
||||||
/// * `SameSite`: `Strict`
|
/// * `SameSite`: `Strict`
|
||||||
///
|
///
|
||||||
/// Furthermore, if TLS is enabled, the `Secure` cookie flag is set.
|
/// 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.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
|
@ -309,7 +304,7 @@ impl<'a> CookieJar<'a> {
|
||||||
/// ```
|
/// ```
|
||||||
pub fn add<C: Into<Cookie<'static>>>(&self, cookie: C) {
|
pub fn add<C: Into<Cookie<'static>>>(&self, cookie: C) {
|
||||||
let mut cookie = cookie.into();
|
let mut cookie = cookie.into();
|
||||||
Self::set_defaults(self.config, &mut cookie);
|
self.set_defaults(&mut cookie);
|
||||||
self.ops.lock().push(Op::Add(cookie, false));
|
self.ops.lock().push(Op::Add(cookie, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,9 +322,10 @@ impl<'a> CookieJar<'a> {
|
||||||
/// * `HttpOnly`: `true`
|
/// * `HttpOnly`: `true`
|
||||||
/// * `Expires`: 1 week from now
|
/// * `Expires`: 1 week from now
|
||||||
///
|
///
|
||||||
/// Furthermore, if TLS is enabled, the `Secure` cookie flag is set. These
|
/// Furthermore, if TLS is enabled or handled by a proxy (as determined by
|
||||||
/// defaults ensure maximum usability and security. For additional security,
|
/// [`Request::context_is_likely_secure()`]), the `Secure` cookie flag is set.
|
||||||
/// you may wish to set the `secure` flag.
|
/// These defaults ensure maximum usability and security. For additional
|
||||||
|
/// security, you may wish to set the `secure` flag explicitly.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
|
@ -346,7 +342,7 @@ impl<'a> CookieJar<'a> {
|
||||||
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
||||||
pub fn add_private<C: Into<Cookie<'static>>>(&self, cookie: C) {
|
pub fn add_private<C: Into<Cookie<'static>>>(&self, cookie: C) {
|
||||||
let mut cookie = cookie.into();
|
let mut cookie = cookie.into();
|
||||||
Self::set_private_defaults(self.config, &mut cookie);
|
self.set_private_defaults(&mut cookie);
|
||||||
self.ops.lock().push(Op::Add(cookie, true));
|
self.ops.lock().push(Op::Add(cookie, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -476,7 +472,7 @@ impl<'a> CookieJar<'a> {
|
||||||
Op::Add(c, false) => jar.add(c),
|
Op::Add(c, false) => jar.add(c),
|
||||||
#[cfg(feature = "secrets")]
|
#[cfg(feature = "secrets")]
|
||||||
Op::Add(c, true) => {
|
Op::Add(c, true) => {
|
||||||
jar.private_mut(&self.config.secret_key.key).add(c);
|
jar.private_mut(&self.state.config.secret_key.key).add(c);
|
||||||
}
|
}
|
||||||
Op::Remove(mut c, _) => {
|
Op::Remove(mut c, _) => {
|
||||||
if self.jar.get(c.name()).is_some() {
|
if self.jar.get(c.name()).is_some() {
|
||||||
|
@ -505,7 +501,7 @@ impl<'a> CookieJar<'a> {
|
||||||
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn add_original_private(&mut self, cookie: Cookie<'static>) {
|
pub(crate) fn add_original_private(&mut self, cookie: Cookie<'static>) {
|
||||||
self.jar.private_mut(&self.config.secret_key.key).add_original(cookie);
|
self.jar.private_mut(&self.state.config.secret_key.key).add_original(cookie);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For each property mentioned below, this method checks if there is a
|
/// For each property mentioned below, this method checks if there is a
|
||||||
|
@ -515,8 +511,9 @@ impl<'a> CookieJar<'a> {
|
||||||
/// * `path`: `"/"`
|
/// * `path`: `"/"`
|
||||||
/// * `SameSite`: `Strict`
|
/// * `SameSite`: `Strict`
|
||||||
///
|
///
|
||||||
/// Furthermore, if TLS is enabled, the `Secure` cookie flag is set.
|
/// Furthermore, if TLS is enabled or handled by a proxy (as determined by
|
||||||
fn set_defaults(config: &Config, cookie: &mut Cookie<'static>) {
|
/// [`Request::context_is_likely_secure()`]), the `Secure` cookie flag is set.
|
||||||
|
fn set_defaults(&self, cookie: &mut Cookie<'static>) {
|
||||||
if cookie.path().is_none() {
|
if cookie.path().is_none() {
|
||||||
cookie.set_path("/");
|
cookie.set_path("/");
|
||||||
}
|
}
|
||||||
|
@ -525,7 +522,7 @@ impl<'a> CookieJar<'a> {
|
||||||
cookie.set_same_site(SameSite::Strict);
|
cookie.set_same_site(SameSite::Strict);
|
||||||
}
|
}
|
||||||
|
|
||||||
if cookie.secure().is_none() && config.tls_enabled() {
|
if cookie.secure().is_none() && self.state.secure {
|
||||||
cookie.set_secure(true);
|
cookie.set_secure(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -554,17 +551,12 @@ impl<'a> CookieJar<'a> {
|
||||||
/// * `HttpOnly`: `true`
|
/// * `HttpOnly`: `true`
|
||||||
/// * `Expires`: 1 week from now
|
/// * `Expires`: 1 week from now
|
||||||
///
|
///
|
||||||
/// Furthermore, if TLS is enabled, the `Secure` cookie flag is set.
|
/// Furthermore, if TLS is enabled or handled by a proxy (as determined by
|
||||||
|
/// [`Request::context_is_likely_secure()`]), the `Secure` cookie flag is set.
|
||||||
#[cfg(feature = "secrets")]
|
#[cfg(feature = "secrets")]
|
||||||
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
||||||
fn set_private_defaults(config: &Config, cookie: &mut Cookie<'static>) {
|
fn set_private_defaults(&self, cookie: &mut Cookie<'static>) {
|
||||||
if cookie.path().is_none() {
|
self.set_defaults(cookie);
|
||||||
cookie.set_path("/");
|
|
||||||
}
|
|
||||||
|
|
||||||
if cookie.same_site().is_none() {
|
|
||||||
cookie.set_same_site(SameSite::Strict);
|
|
||||||
}
|
|
||||||
|
|
||||||
if cookie.http_only().is_none() {
|
if cookie.http_only().is_none() {
|
||||||
cookie.set_http_only(true);
|
cookie.set_http_only(true);
|
||||||
|
@ -573,10 +565,6 @@ impl<'a> CookieJar<'a> {
|
||||||
if cookie.expires().is_none() {
|
if cookie.expires().is_none() {
|
||||||
cookie.set_expires(time::OffsetDateTime::now_utc() + time::Duration::weeks(1));
|
cookie.set_expires(time::OffsetDateTime::now_utc() + time::Duration::weeks(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
if cookie.secure().is_none() && config.tls_enabled() {
|
|
||||||
cookie.set_secure(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -593,5 +581,22 @@ impl fmt::Debug for CookieJar<'_> {
|
||||||
.field("pending", &pending)
|
.field("pending", &pending)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Clone for CookieJar<'a> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
CookieJar {
|
||||||
|
jar: self.jar.clone(),
|
||||||
|
ops: Mutex::new(self.ops.lock().clone()),
|
||||||
|
state: self.state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Op {
|
||||||
|
fn cookie(&self) -> &Cookie<'static> {
|
||||||
|
match self {
|
||||||
|
Op::Add(c, _) | Op::Remove(c, _) => c
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,8 +87,11 @@ impl<'c> LocalResponse<'c> {
|
||||||
let request: &'c Request<'c> = unsafe { &*(&*boxed_req as *const _) };
|
let request: &'c Request<'c> = unsafe { &*(&*boxed_req as *const _) };
|
||||||
|
|
||||||
async move {
|
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.
|
||||||
let response: Response<'c> = f(request).await;
|
let response: Response<'c> = f(request).await;
|
||||||
let mut cookies = CookieJar::new(request.rocket().config());
|
let mut cookies = CookieJar::new(None, request.rocket());
|
||||||
for cookie in response.cookies() {
|
for cookie in response.cookies() {
|
||||||
cookies.add_original(cookie.into_owned());
|
cookies.add_original(cookie.into_owned());
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,9 +181,8 @@ macro_rules! pub_client_impl {
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn cookies(&self) -> crate::http::CookieJar<'_> {
|
pub fn cookies(&self) -> crate::http::CookieJar<'_> {
|
||||||
let config = &self.rocket().config();
|
|
||||||
let jar = self._with_raw_cookies(|jar| jar.clone());
|
let jar = self._with_raw_cookies(|jar| jar.clone());
|
||||||
crate::http::CookieJar::from(jar, config)
|
crate::http::CookieJar::new(Some(jar), self.rocket())
|
||||||
}
|
}
|
||||||
|
|
||||||
req_method!($import, "GET", get, Method::Get);
|
req_method!($import, "GET", get, Method::Get);
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::{Request, Route};
|
||||||
use crate::outcome::{self, Outcome::*};
|
use crate::outcome::{self, Outcome::*};
|
||||||
|
|
||||||
use crate::http::uri::{Host, Origin};
|
use crate::http::uri::{Host, Origin};
|
||||||
use crate::http::{Status, ContentType, Accept, Method, CookieJar};
|
use crate::http::{Status, ContentType, Accept, Method, ProxyProto, CookieJar};
|
||||||
|
|
||||||
/// Type alias for the `Outcome` of a `FromRequest` conversion.
|
/// Type alias for the `Outcome` of a `FromRequest` conversion.
|
||||||
pub type Outcome<S, E> = outcome::Outcome<S, (Status, E), Status>;
|
pub type Outcome<S, E> = outcome::Outcome<S, (Status, E), Status>;
|
||||||
|
@ -160,6 +160,12 @@ pub type Outcome<S, E> = outcome::Outcome<S, (Status, E), Status>;
|
||||||
/// via [`Request::client_ip()`]. If the client's IP address is not known,
|
/// via [`Request::client_ip()`]. If the client's IP address is not known,
|
||||||
/// the request is forwarded with a 500 Internal Server Error status.
|
/// the request is forwarded with a 500 Internal Server Error 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.
|
||||||
|
///
|
||||||
/// * **SocketAddr**
|
/// * **SocketAddr**
|
||||||
///
|
///
|
||||||
/// Extracts the remote address of the incoming request as a [`SocketAddr`]
|
/// Extracts the remote address of the incoming request as a [`SocketAddr`]
|
||||||
|
@ -470,6 +476,18 @@ impl<'r> FromRequest<'r> for IpAddr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[crate::async_trait]
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[crate::async_trait]
|
#[crate::async_trait]
|
||||||
impl<'r> FromRequest<'r> for SocketAddr {
|
impl<'r> FromRequest<'r> for SocketAddr {
|
||||||
type Error = Infallible;
|
type Error = Infallible;
|
||||||
|
|
|
@ -13,9 +13,8 @@ use crate::request::{FromParam, FromSegments, FromRequest, Outcome};
|
||||||
use crate::form::{self, ValueField, FromForm};
|
use crate::form::{self, ValueField, FromForm};
|
||||||
use crate::data::Limits;
|
use crate::data::Limits;
|
||||||
|
|
||||||
use crate::http::{hyper, Method, Header, HeaderMap};
|
use crate::http::{hyper, Method, Header, HeaderMap, ProxyProto};
|
||||||
use crate::http::{ContentType, Accept, MediaType, CookieJar, Cookie};
|
use crate::http::{ContentType, Accept, MediaType, CookieJar, Cookie};
|
||||||
use crate::http::uncased::UncasedStr;
|
|
||||||
use crate::http::private::Certificates;
|
use crate::http::private::Certificates;
|
||||||
use crate::http::uri::{fmt::Path, Origin, Segments, Host, Authority};
|
use crate::http::uri::{fmt::Path, Origin, Segments, Host, Authority};
|
||||||
|
|
||||||
|
@ -97,7 +96,7 @@ impl<'r> Request<'r> {
|
||||||
state: RequestState {
|
state: RequestState {
|
||||||
rocket,
|
rocket,
|
||||||
route: Atomic::new(None),
|
route: Atomic::new(None),
|
||||||
cookies: CookieJar::new(rocket.config()),
|
cookies: CookieJar::new(None, rocket),
|
||||||
accept: InitCell::new(),
|
accept: InitCell::new(),
|
||||||
content_type: InitCell::new(),
|
content_type: InitCell::new(),
|
||||||
cache: Arc::new(<TypeMap![Send + Sync]>::new()),
|
cache: Arc::new(<TypeMap![Send + Sync]>::new()),
|
||||||
|
@ -386,6 +385,85 @@ impl<'r> Request<'r> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the [`ProxyProto`] associated with the current request.
|
||||||
|
///
|
||||||
|
/// 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`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use rocket::http::{Header, ProxyProto};
|
||||||
|
///
|
||||||
|
/// # let c = rocket::local::blocking::Client::debug_with(vec![]).unwrap();
|
||||||
|
/// # let req = c.get("/");
|
||||||
|
/// // By default, no `proxy_proto_header` is configured.
|
||||||
|
/// let req = req.header(Header::new("x-forwarded-proto", "https"));
|
||||||
|
/// assert_eq!(req.proxy_proto(), None);
|
||||||
|
///
|
||||||
|
/// // We can configure one by setting the `proxy_proto_header` parameter.
|
||||||
|
/// // Here we set it to `x-forwarded-proto`, considered de-facto standard.
|
||||||
|
/// # let figment = rocket::figment::Figment::from(rocket::Config::debug_default());
|
||||||
|
/// let figment = figment.merge(("proxy_proto_header", "x-forwarded-proto"));
|
||||||
|
/// # let c = rocket::local::blocking::Client::debug(rocket::custom(figment)).unwrap();
|
||||||
|
/// # let req = c.get("/");
|
||||||
|
/// let req = req.header(Header::new("x-forwarded-proto", "https"));
|
||||||
|
/// assert_eq!(req.proxy_proto(), Some(ProxyProto::Https));
|
||||||
|
///
|
||||||
|
/// # let req = c.get("/");
|
||||||
|
/// let req = req.header(Header::new("x-forwarded-proto", "http"));
|
||||||
|
/// assert_eq!(req.proxy_proto(), Some(ProxyProto::Http));
|
||||||
|
///
|
||||||
|
/// # let req = c.get("/");
|
||||||
|
/// let req = req.header(Header::new("x-forwarded-proto", "xproto"));
|
||||||
|
/// assert_eq!(req.proxy_proto(), Some(ProxyProto::Unknown("xproto".into())));
|
||||||
|
/// ```
|
||||||
|
pub fn proxy_proto(&self) -> Option<ProxyProto<'_>> {
|
||||||
|
self.rocket()
|
||||||
|
.config
|
||||||
|
.proxy_proto_header
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|header| self.headers().get_one(header.as_str()))
|
||||||
|
.map(ProxyProto::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether we are *likely* in a secure context.
|
||||||
|
///
|
||||||
|
/// A request is in a "secure context" if it was initially sent over a
|
||||||
|
/// secure (TLS, via HTTPS) connection. If TLS is configured and enabled,
|
||||||
|
/// then the request is guaranteed to be in a secure context. Otherwise, if
|
||||||
|
/// [`Request::proxy_proto()`] evaluates to `Https`, then we are _likely_ to
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use rocket::http::{Header, ProxyProto};
|
||||||
|
///
|
||||||
|
/// # let client = rocket::local::blocking::Client::debug_with(vec![]).unwrap();
|
||||||
|
/// # let req = client.get("/");
|
||||||
|
/// // If TLS and proxy_proto are disabled, we are not in a secure context.
|
||||||
|
/// assert_eq!(req.context_is_likely_secure(), false);
|
||||||
|
///
|
||||||
|
/// // Configuring proxy_proto and receiving a header value of `https` is
|
||||||
|
/// // interpreted as likely being in a secure context.
|
||||||
|
/// // Here we set it to `x-forwarded-proto`, considered de-facto standard.
|
||||||
|
/// # let figment = rocket::figment::Figment::from(rocket::Config::debug_default());
|
||||||
|
/// let figment = figment.merge(("proxy_proto_header", "x-forwarded-proto"));
|
||||||
|
/// # let c = rocket::local::blocking::Client::debug(rocket::custom(figment)).unwrap();
|
||||||
|
/// # let req = c.get("/");
|
||||||
|
/// let req = req.header(Header::new("x-forwarded-proto", "https"));
|
||||||
|
/// assert_eq!(req.context_is_likely_secure(), true);
|
||||||
|
/// ```
|
||||||
|
pub fn context_is_likely_secure(&self) -> bool {
|
||||||
|
self.cookies().state.secure
|
||||||
|
}
|
||||||
|
|
||||||
/// Attempts to return the client's IP address by first inspecting the
|
/// Attempts to return the client's IP address by first inspecting the
|
||||||
/// [`ip_header`](crate::Config::ip_header) and then using the remote
|
/// [`ip_header`](crate::Config::ip_header) and then using the remote
|
||||||
/// connection's IP address. Note that the built-in `IpAddr` request guard
|
/// connection's IP address. Note that the built-in `IpAddr` request guard
|
||||||
|
@ -497,7 +575,7 @@ impl<'r> Request<'r> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn add_header<'h: 'r, H: Into<Header<'h>>>(&mut self, header: H) {
|
pub fn add_header<'h: 'r, H: Into<Header<'h>>>(&mut self, header: H) {
|
||||||
let header = header.into();
|
let header = header.into();
|
||||||
self.bust_header_cache(header.name(), false);
|
self.bust_header_cache(&header, false);
|
||||||
self.headers.add(header);
|
self.headers.add(header);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -526,7 +604,7 @@ impl<'r> Request<'r> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn replace_header<'h: 'r, H: Into<Header<'h>>>(&mut self, header: H) {
|
pub fn replace_header<'h: 'r, H: Into<Header<'h>>>(&mut self, header: H) {
|
||||||
let header = header.into();
|
let header = header.into();
|
||||||
self.bust_header_cache(header.name(), true);
|
self.bust_header_cache(&header, true);
|
||||||
self.headers.replace(header);
|
self.headers.replace(header);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -547,9 +625,9 @@ impl<'r> Request<'r> {
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn content_type(&self) -> Option<&ContentType> {
|
pub fn content_type(&self) -> Option<&ContentType> {
|
||||||
self.state.content_type.get_or_init(|| {
|
self.state.content_type
|
||||||
self.headers().get_one("Content-Type").and_then(|v| v.parse().ok())
|
.get_or_init(|| self.headers().get_one("Content-Type").and_then(|v| v.parse().ok()))
|
||||||
}).as_ref()
|
.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the Accept header of `self`. If the header is not present,
|
/// Returns the Accept header of `self`. If the header is not present,
|
||||||
|
@ -567,9 +645,9 @@ impl<'r> Request<'r> {
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn accept(&self) -> Option<&Accept> {
|
pub fn accept(&self) -> Option<&Accept> {
|
||||||
self.state.accept.get_or_init(|| {
|
self.state.accept
|
||||||
self.headers().get_one("Accept").and_then(|v| v.parse().ok())
|
.get_or_init(|| self.headers().get_one("Accept").and_then(|v| v.parse().ok()))
|
||||||
}).as_ref()
|
.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the media type "format" of the request.
|
/// Returns the media type "format" of the request.
|
||||||
|
@ -925,15 +1003,19 @@ impl<'r> Request<'r> {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
impl<'r> Request<'r> {
|
impl<'r> Request<'r> {
|
||||||
/// Resets the cached value (if any) for the header with name `name`.
|
/// Resets the cached value (if any) for the header with name `name`.
|
||||||
fn bust_header_cache(&mut self, name: &UncasedStr, replace: bool) {
|
fn bust_header_cache(&mut self, header: &Header<'_>, replace: bool) {
|
||||||
if name == "Content-Type" {
|
if header.name() == "Content-Type" {
|
||||||
if self.content_type().is_none() || replace {
|
if self.content_type().is_none() || replace {
|
||||||
self.state.content_type = InitCell::new();
|
self.state.content_type = InitCell::new();
|
||||||
}
|
}
|
||||||
} else if name == "Accept" {
|
} else if header.name() == "Accept" {
|
||||||
if self.accept().is_none() || replace {
|
if self.accept().is_none() || replace {
|
||||||
self.state.accept = InitCell::new();
|
self.state.accept = InitCell::new();
|
||||||
}
|
}
|
||||||
|
} else if Some(header.name()) == self.rocket().config.proxy_proto_header.as_deref() {
|
||||||
|
if !self.cookies().state.secure || replace {
|
||||||
|
self.cookies_mut().state.secure |= ProxyProto::from(header.value()).is_https();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
use rocket::http::ProxyProto;
|
||||||
|
|
||||||
|
#[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())
|
||||||
|
}
|
||||||
|
|
||||||
|
mod tests {
|
||||||
|
use rocket::{Rocket, Build, Route};
|
||||||
|
use rocket::http::Header;
|
||||||
|
use rocket::local::blocking::Client;
|
||||||
|
use rocket::figment::Figment;
|
||||||
|
|
||||||
|
fn routes() -> Vec<Route> {
|
||||||
|
routes![super::inspect_proto]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rocket_with_proto_header(header: Option<&'static str>) -> Rocket<Build> {
|
||||||
|
let mut config = rocket::Config::debug_default();
|
||||||
|
config.proxy_proto_header = header.map(|h| h.into());
|
||||||
|
rocket::custom(config).mount("/", routes())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_proxy_proto_header_works() {
|
||||||
|
let rocket = rocket_with_proto_header(Some("X-Url-Scheme"));
|
||||||
|
let client = Client::debug(rocket).unwrap();
|
||||||
|
let response = client.get("/")
|
||||||
|
.header(Header::new("X-Forwarded-Proto", "https"))
|
||||||
|
.header(Header::new("X-Url-Scheme", "http"))
|
||||||
|
.dispatch();
|
||||||
|
|
||||||
|
assert_eq!(response.into_string().unwrap(), "http");
|
||||||
|
|
||||||
|
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>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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"))
|
||||||
|
.dispatch();
|
||||||
|
|
||||||
|
assert_eq!(response.into_string().unwrap(), "https");
|
||||||
|
|
||||||
|
let config = Figment::from(rocket::Config::debug_default())
|
||||||
|
.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"))
|
||||||
|
.dispatch();
|
||||||
|
|
||||||
|
assert_eq!(response.into_string().unwrap(), "https");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_default_proxy_proto_header_works() {
|
||||||
|
let client = Client::debug_with(routes()).unwrap();
|
||||||
|
let response = client
|
||||||
|
.get("/")
|
||||||
|
.header(Header::new("X-Forwarded-Proto", "https"))
|
||||||
|
.dispatch();
|
||||||
|
|
||||||
|
assert_eq!(response.into_string(), Some("<none>".into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_no_proxy_proto_header_works() {
|
||||||
|
let client = Client::debug(rocket_with_proto_header(None)).unwrap();
|
||||||
|
let response = client.get("/")
|
||||||
|
.header(Header::new("X-Forwarded-Proto", "https"))
|
||||||
|
.dispatch();
|
||||||
|
|
||||||
|
assert_eq!(response.into_string(), Some("<none>".into()));
|
||||||
|
|
||||||
|
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("/")
|
||||||
|
.header(Header::new("X-Forwarded-Proto", "https"))
|
||||||
|
.dispatch();
|
||||||
|
|
||||||
|
assert_eq!(response.into_string(), Some("<none>".into()));
|
||||||
|
|
||||||
|
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("/")
|
||||||
|
.header(Header::new("x-Forwarded-Proto", "https"))
|
||||||
|
.dispatch();
|
||||||
|
|
||||||
|
assert_eq!(response.into_string(), Some("https".into()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,28 +17,31 @@ is configured with. This means that no matter which configuration provider
|
||||||
Rocket is asked to use, it must be able to read the following configuration
|
Rocket is asked to use, it must be able to read the following configuration
|
||||||
values:
|
values:
|
||||||
|
|
||||||
| key | kind | description | debug/release default |
|
| key | kind | description | debug/release default |
|
||||||
|-----------------|-------------------|-------------------------------------------------|-------------------------|
|
|----------------------|-------------------|-------------------------------------------------|-------------------------|
|
||||||
| `address` | `IpAddr` | IP address to serve on | `127.0.0.1` |
|
| `address` | `IpAddr` | IP address to serve on. | `127.0.0.1` |
|
||||||
| `port` | `u16` | Port to serve on. | `8000` |
|
| `port` | `u16` | Port to serve on. | `8000` |
|
||||||
| `workers`* | `usize` | Number of threads to use for executing futures. | cpu core count |
|
| `workers`* | `usize` | Number of threads to use for executing futures. | cpu core count |
|
||||||
| `max_blocking`* | `usize` | Limit on threads to start for blocking tasks. | `512` |
|
| `max_blocking`* | `usize` | Limit on threads to start for blocking tasks. | `512` |
|
||||||
| `ident` | `string`, `false` | If and how to identify via the `Server` header. | `"Rocket"` |
|
| `ident` | `string`, `false` | If and how to identify via the `Server` header. | `"Rocket"` |
|
||||||
| `ip_header` | `string`, `false` | IP header to inspect to get [client's real IP]. | `"X-Real-IP"` |
|
| `ip_header` | `string`, `false` | IP header to inspect to get [client's real IP]. | `"X-Real-IP"` |
|
||||||
| `keep_alive` | `u32` | Keep-alive timeout seconds; disabled when `0`. | `5` |
|
| `proxy_proto_header` | `string`, `false` | Header identifying [client to proxy protocol]. | `None` |
|
||||||
| `log_level` | [`LogLevel`] | Max level to log. (off/normal/debug/critical) | `normal`/`critical` |
|
| `keep_alive` | `u32` | Keep-alive timeout seconds; disabled when `0`. | `5` |
|
||||||
| `cli_colors` | [`CliColors`] | Whether to use colors and emoji when logging. | `"auto"` |
|
| `log_level` | [`LogLevel`] | Max level to log. (off/normal/debug/critical) | `normal`/`critical` |
|
||||||
| `secret_key` | [`SecretKey`] | Secret key for signing and encrypting values. | `None` |
|
| `cli_colors` | [`CliColors`] | Whether to use colors and emoji when logging. | `"auto"` |
|
||||||
| `tls` | [`TlsConfig`] | TLS configuration, if any. | `None` |
|
| `secret_key` | [`SecretKey`] | Secret key for signing and encrypting values. | `None` |
|
||||||
| `limits` | [`Limits`] | Streaming read size limits. | [`Limits::default()`] |
|
| `tls` | [`TlsConfig`] | TLS configuration, if any. | `None` |
|
||||||
| `limits.$name` | `&str`/`uint` | Read limit for `$name`. | form = "32KiB" |
|
| `limits` | [`Limits`] | Streaming read size limits. | [`Limits::default()`] |
|
||||||
| `ctrlc` | `bool` | Whether `ctrl-c` initiates a server shutdown. | `true` |
|
| `limits.$name` | `&str`/`uint` | Read limit for `$name`. | form = "32KiB" |
|
||||||
| `shutdown`* | [`Shutdown`] | Graceful shutdown configuration. | [`Shutdown::default()`] |
|
| `ctrlc` | `bool` | Whether `ctrl-c` initiates a server shutdown. | `true` |
|
||||||
|
| `shutdown`* | [`Shutdown`] | Graceful shutdown configuration. | [`Shutdown::default()`] |
|
||||||
|
|
||||||
|
|
||||||
<small>* Note: the `workers`, `max_blocking`, and `shutdown.force` configuration
|
<small>* Note: the `workers`, `max_blocking`, and `shutdown.force` configuration
|
||||||
parameters are only read from the [default provider](#default-provider).</small>
|
parameters are only read from the [default provider](#default-provider).</small>
|
||||||
|
|
||||||
[client's real IP]: @api/rocket/request/struct.Request.html#method.real_ip
|
[client's real IP]: @api/rocket/request/struct.Request.html#method.real_ip
|
||||||
|
[client to proxy protocol]: @api/rocket/request/struct.Request.html#method.proxy_proto
|
||||||
|
|
||||||
### Profiles
|
### Profiles
|
||||||
|
|
||||||
|
@ -130,6 +133,7 @@ port = 9001
|
||||||
[release]
|
[release]
|
||||||
port = 9999
|
port = 9999
|
||||||
ip_header = false
|
ip_header = false
|
||||||
|
proxy_proto_header = "X-Forwarded-Proto"
|
||||||
# NOTE: Don't (!) use this key! Generate your own and keep it private!
|
# NOTE: Don't (!) use this key! Generate your own and keep it private!
|
||||||
# e.g. via `head -c64 /dev/urandom | base64`
|
# e.g. via `head -c64 /dev/urandom | base64`
|
||||||
secret_key = "hPrYyЭRiMyµ5sBB1π+CMæ1køFsåqKvBiQJxBVHQk="
|
secret_key = "hPrYyЭRiMyµ5sBB1π+CMæ1køFsåqKvBiQJxBVHQk="
|
||||||
|
@ -148,7 +152,8 @@ workers = 16
|
||||||
max_blocking = 512
|
max_blocking = 512
|
||||||
keep_alive = 5
|
keep_alive = 5
|
||||||
ident = "Rocket"
|
ident = "Rocket"
|
||||||
ip_header = "X-Real-IP" # set to `false` to disable
|
ip_header = "X-Real-IP" # set to `false` or `None` to disable
|
||||||
|
proxy_proto_header = `false` # set to `false` or `None` to disable
|
||||||
log_level = "normal"
|
log_level = "normal"
|
||||||
temp_dir = "/tmp"
|
temp_dir = "/tmp"
|
||||||
cli_colors = true
|
cli_colors = true
|
||||||
|
@ -358,6 +363,25 @@ mutual TLS.
|
||||||
|
|
||||||
[`mtls::Certificate`]: @api/rocket/mtls/struct.Certificate.html
|
[`mtls::Certificate`]: @api/rocket/mtls/struct.Certificate.html
|
||||||
|
|
||||||
|
### 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:
|
||||||
|
|
||||||
|
```toml,ignore
|
||||||
|
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
|
||||||
|
|
||||||
### Workers
|
### Workers
|
||||||
|
|
||||||
The `workers` parameter sets the number of threads used for parallel task
|
The `workers` parameter sets the number of threads used for parallel task
|
||||||
|
|
Loading…
Reference in New Issue