mirror of https://github.com/rwf2/Rocket.git
parent
bbc36ba27f
commit
7ffe3a7360
|
@ -4,20 +4,8 @@
|
||||||
//! These types will, with certainty, be removed with time, but they reside here
|
//! These types will, with certainty, be removed with time, but they reside here
|
||||||
//! while necessary.
|
//! while necessary.
|
||||||
|
|
||||||
#[doc(hidden)] pub use hyper::{Body, Error, Request, Response, Version};
|
#[doc(hidden)] pub use hyper::*;
|
||||||
#[doc(hidden)] pub use hyper::body::{Bytes, HttpBody, Sender as BodySender};
|
#[doc(hidden)] pub use http::*;
|
||||||
#[doc(hidden)] pub use hyper::rt::Executor;
|
|
||||||
#[doc(hidden)] pub use hyper::server::Server;
|
|
||||||
#[doc(hidden)] pub use hyper::service::{make_service_fn, service_fn, Service};
|
|
||||||
|
|
||||||
#[doc(hidden)] pub use http::header::HeaderMap;
|
|
||||||
#[doc(hidden)] pub use http::header::HeaderName as HeaderName;
|
|
||||||
#[doc(hidden)] pub use http::header::HeaderValue as HeaderValue;
|
|
||||||
#[doc(hidden)] pub use http::method::Method;
|
|
||||||
#[doc(hidden)] pub use http::request::Parts as RequestParts;
|
|
||||||
#[doc(hidden)] pub use http::response::Builder as ResponseBuilder;
|
|
||||||
#[doc(hidden)] pub use http::status::StatusCode;
|
|
||||||
#[doc(hidden)] pub use http::uri::{Uri, Parts as UriParts};
|
|
||||||
|
|
||||||
/// Reexported http header types.
|
/// Reexported http header types.
|
||||||
pub mod header {
|
pub mod header {
|
||||||
|
|
|
@ -89,6 +89,98 @@ pub enum Error {
|
||||||
Trailing(usize),
|
Trailing(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A request guard for validated, verified client certificates.
|
||||||
|
///
|
||||||
|
/// This type is a wrapper over [`x509::TbsCertificate`] with convenient
|
||||||
|
/// methods and complete documentation. Should the data exposed by the inherent
|
||||||
|
/// methods not suffice, this type derefs to [`x509::TbsCertificate`].
|
||||||
|
///
|
||||||
|
/// # Request Guard
|
||||||
|
///
|
||||||
|
/// The request guard implementation succeeds if:
|
||||||
|
///
|
||||||
|
/// * The client presents certificates.
|
||||||
|
/// * The certificates are active and not yet expired.
|
||||||
|
/// * The client's certificate chain was signed by the CA identified by the
|
||||||
|
/// configured `ca_certs` and with respect to SNI, if any. See [module level
|
||||||
|
/// docs](self) for configuration details.
|
||||||
|
///
|
||||||
|
/// If the client does not present certificates, the guard _forwards_.
|
||||||
|
///
|
||||||
|
/// If the certificate chain fails to validate or verify, the guard _fails_ with
|
||||||
|
/// the respective [`Error`].
|
||||||
|
///
|
||||||
|
/// # Wrapping
|
||||||
|
///
|
||||||
|
/// To implement roles, the `Certificate` guard can be wrapped with a more
|
||||||
|
/// semantically meaningful type with extra validation. For example, if a
|
||||||
|
/// certificate with a specific serial number is known to belong to an
|
||||||
|
/// administrator, a `CertifiedAdmin` type can authorize as follow:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
/// use rocket::mtls::{self, bigint::BigUint, Certificate};
|
||||||
|
/// use rocket::request::{Request, FromRequest, Outcome};
|
||||||
|
/// use rocket::outcome::try_outcome;
|
||||||
|
///
|
||||||
|
/// // The serial number for the certificate issued to the admin.
|
||||||
|
/// const ADMIN_SERIAL: &str = "65828378108300243895479600452308786010218223563";
|
||||||
|
///
|
||||||
|
/// // A request guard that authenticates and authorizes an administrator.
|
||||||
|
/// struct CertifiedAdmin<'r>(Certificate<'r>);
|
||||||
|
///
|
||||||
|
/// #[rocket::async_trait]
|
||||||
|
/// impl<'r> FromRequest<'r> for CertifiedAdmin<'r> {
|
||||||
|
/// type Error = mtls::Error;
|
||||||
|
///
|
||||||
|
/// async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||||
|
/// let cert = try_outcome!(req.guard::<Certificate<'r>>().await);
|
||||||
|
/// if let Some(true) = cert.has_serial(ADMIN_SERIAL) {
|
||||||
|
/// Outcome::Success(CertifiedAdmin(cert))
|
||||||
|
/// } else {
|
||||||
|
/// Outcome::Forward(())
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[get("/admin")]
|
||||||
|
/// fn admin(admin: CertifiedAdmin<'_>) {
|
||||||
|
/// // This handler can only execute if an admin is authenticated.
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[get("/admin", rank = 2)]
|
||||||
|
/// fn unauthorized(user: Option<Certificate<'_>>) {
|
||||||
|
/// // This handler always executes, whether there's a non-admin user that's
|
||||||
|
/// // authenticated (user = Some()) or not (user = None).
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// To retrieve certificate data in a route, use `Certificate` as a guard:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// # use rocket::get;
|
||||||
|
/// use rocket::mtls::{self, Certificate};
|
||||||
|
///
|
||||||
|
/// #[get("/auth")]
|
||||||
|
/// fn auth(cert: Certificate<'_>) {
|
||||||
|
/// // This handler only runs when a valid certificate was presented.
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[get("/maybe")]
|
||||||
|
/// fn maybe_auth(cert: Option<Certificate<'_>>) {
|
||||||
|
/// // This handler runs even if no certificate was presented or an invalid
|
||||||
|
/// // certificate was presented.
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[get("/ok")]
|
||||||
|
/// fn ok_auth(cert: mtls::Result<Certificate<'_>>) {
|
||||||
|
/// // This handler does not run if a certificate was not presented but
|
||||||
|
/// // _does_ run if a valid (Ok) or invalid (Err) one was presented.
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct Certificate<'a>(X509Certificate<'a>);
|
pub struct Certificate<'a>(X509Certificate<'a>);
|
||||||
|
@ -138,26 +230,136 @@ impl<'a> Certificate<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the serial number of the X.509 certificate.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// # use rocket::get;
|
||||||
|
/// use rocket::mtls::Certificate;
|
||||||
|
///
|
||||||
|
/// #[get("/auth")]
|
||||||
|
/// fn auth(cert: Certificate<'_>) {
|
||||||
|
/// let cert = cert.serial();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub fn serial(&self) -> &bigint::BigUint {
|
pub fn serial(&self) -> &bigint::BigUint {
|
||||||
&self.inner().serial
|
&self.inner().serial
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the version of the X.509 certificate.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// # use rocket::get;
|
||||||
|
/// use rocket::mtls::Certificate;
|
||||||
|
///
|
||||||
|
/// #[get("/auth")]
|
||||||
|
/// fn auth(cert: Certificate<'_>) {
|
||||||
|
/// let cert = cert.version();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub fn version(&self) -> u32 {
|
pub fn version(&self) -> u32 {
|
||||||
self.inner().version.0
|
self.inner().version.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the subject (a "DN" or "Distinguised Name") of the X.509
|
||||||
|
/// certificate.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// # use rocket::get;
|
||||||
|
/// use rocket::mtls::Certificate;
|
||||||
|
///
|
||||||
|
/// #[get("/auth")]
|
||||||
|
/// fn auth(cert: Certificate<'_>) {
|
||||||
|
/// if let Some(name) = cert.subject().common_name() {
|
||||||
|
/// println!("Hello, {}!", name);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub fn subject(&self) -> &Name<'a> {
|
pub fn subject(&self) -> &Name<'a> {
|
||||||
Name::ref_cast(&self.inner().subject)
|
Name::ref_cast(&self.inner().subject)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the issuer (a "DN" or "Distinguised Name") of the X.509
|
||||||
|
/// certificate.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// # use rocket::get;
|
||||||
|
/// use rocket::mtls::Certificate;
|
||||||
|
///
|
||||||
|
/// #[get("/auth")]
|
||||||
|
/// fn auth(cert: Certificate<'_>) {
|
||||||
|
/// if let Some(name) = cert.issuer().common_name() {
|
||||||
|
/// println!("Issued by: {}", name);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub fn issuer(&self) -> &Name<'a> {
|
pub fn issuer(&self) -> &Name<'a> {
|
||||||
Name::ref_cast(&self.inner().issuer)
|
Name::ref_cast(&self.inner().issuer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a map of the extensions in the X.509 certificate.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// # use rocket::get;
|
||||||
|
/// use rocket::mtls::{oid, x509, Certificate};
|
||||||
|
///
|
||||||
|
/// #[get("/auth")]
|
||||||
|
/// fn auth(cert: Certificate<'_>) {
|
||||||
|
/// let subject_alt = cert.extensions()
|
||||||
|
/// .get(&oid::OID_X509_EXT_SUBJECT_ALT_NAME)
|
||||||
|
/// .and_then(|e| match e.parsed_extension() {
|
||||||
|
/// x509::ParsedExtension::SubjectAlternativeName(s) => Some(s),
|
||||||
|
/// _ => None
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// if let Some(subject_alt) = subject_alt {
|
||||||
|
/// for name in &subject_alt.general_names {
|
||||||
|
/// if let x509::GeneralName::RFC822Name(name) = name {
|
||||||
|
/// println!("An email, perhaps? {}", name);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub fn extensions(&self) -> &HashMap<oid::Oid<'a>, x509::X509Extension<'a>> {
|
pub fn extensions(&self) -> &HashMap<oid::Oid<'a>, x509::X509Extension<'a>> {
|
||||||
&self.inner().extensions
|
&self.inner().extensions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if the certificate has the serial number `number`.
|
||||||
|
///
|
||||||
|
/// If `number` is not a valid unsigned integer in base 10, returns `None`.
|
||||||
|
///
|
||||||
|
/// Otherwise, returns `Some(true)` if it does and `Some(false)` if it does
|
||||||
|
/// not.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// # use rocket::get;
|
||||||
|
/// use rocket::mtls::Certificate;
|
||||||
|
///
|
||||||
|
/// const SERIAL: &str = "65828378108300243895479600452308786010218223563";
|
||||||
|
///
|
||||||
|
/// #[get("/auth")]
|
||||||
|
/// fn auth(cert: Certificate<'_>) {
|
||||||
|
/// if cert.has_serial(SERIAL).unwrap_or(false) {
|
||||||
|
/// println!("certificate has the expected serial number");
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub fn has_serial(&self, number: &str) -> Option<bool> {
|
pub fn has_serial(&self, number: &str) -> Option<bool> {
|
||||||
let uint: bigint::BigUint = number.parse().ok()?;
|
let uint: bigint::BigUint = number.parse().ok()?;
|
||||||
Some(&uint == self.serial())
|
Some(&uint == self.serial())
|
||||||
|
@ -173,22 +375,116 @@ impl<'a> Deref for Certificate<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Name<'a> {
|
impl<'a> Name<'a> {
|
||||||
|
/// Returns the _first_ UTF-8 _string_ common name, if any.
|
||||||
|
///
|
||||||
|
/// Note that common names need not be UTF-8 strings, or strings at all.
|
||||||
|
/// This method returns the first common name attribute that is.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
/// use rocket::mtls::Certificate;
|
||||||
|
///
|
||||||
|
/// #[get("/auth")]
|
||||||
|
/// fn auth(cert: Certificate<'_>) {
|
||||||
|
/// if let Some(name) = cert.subject().common_name() {
|
||||||
|
/// println!("Hello, {}!", name);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub fn common_name(&self) -> Option<&'a str> {
|
pub fn common_name(&self) -> Option<&'a str> {
|
||||||
self.common_names().next()
|
self.common_names().next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over all of the UTF-8 _string_ common names in
|
||||||
|
/// `self`.
|
||||||
|
///
|
||||||
|
/// Note that common names need not be UTF-8 strings, or strings at all.
|
||||||
|
/// This method filters the common names in `self` to those that are. Use
|
||||||
|
/// the raw [`iter_common_name()`](#method.iter_common_name) to iterate over
|
||||||
|
/// all value types.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
/// use rocket::mtls::Certificate;
|
||||||
|
///
|
||||||
|
/// #[get("/auth")]
|
||||||
|
/// fn auth(cert: Certificate<'_>) {
|
||||||
|
/// for name in cert.issuer().common_names() {
|
||||||
|
/// println!("Issued by {}.", name);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub fn common_names(&self) -> impl Iterator<Item = &'a str> + '_ {
|
pub fn common_names(&self) -> impl Iterator<Item = &'a str> + '_ {
|
||||||
self.iter_by_oid(&oid::OID_X509_COMMON_NAME).filter_map(|n| n.as_str().ok())
|
self.iter_by_oid(&oid::OID_X509_COMMON_NAME).filter_map(|n| n.as_str().ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the _first_ UTF-8 _string_ email address, if any.
|
||||||
|
///
|
||||||
|
/// Note that email addresses need not be UTF-8 strings, or strings at all.
|
||||||
|
/// This method returns the first email address attribute that is.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
/// use rocket::mtls::Certificate;
|
||||||
|
///
|
||||||
|
/// #[get("/auth")]
|
||||||
|
/// fn auth(cert: Certificate<'_>) {
|
||||||
|
/// if let Some(email) = cert.subject().email() {
|
||||||
|
/// println!("Hello, {}!", email);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub fn email(&self) -> Option<&'a str> {
|
pub fn email(&self) -> Option<&'a str> {
|
||||||
self.emails().next()
|
self.emails().next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over all of the UTF-8 _string_ email addresses in
|
||||||
|
/// `self`.
|
||||||
|
///
|
||||||
|
/// Note that email addresses need not be UTF-8 strings, or strings at all.
|
||||||
|
/// This method filters the email addresss in `self` to those that are. Use
|
||||||
|
/// the raw [`iter_email()`](#method.iter_email) to iterate over all value
|
||||||
|
/// types.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
/// use rocket::mtls::Certificate;
|
||||||
|
///
|
||||||
|
/// #[get("/auth")]
|
||||||
|
/// fn auth(cert: Certificate<'_>) {
|
||||||
|
/// for email in cert.subject().emails() {
|
||||||
|
/// println!("Reach me at: {}", email);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub fn emails(&self) -> impl Iterator<Item = &'a str> + '_ {
|
pub fn emails(&self) -> impl Iterator<Item = &'a str> + '_ {
|
||||||
self.iter_by_oid(&oid::OID_PKCS9_EMAIL_ADDRESS).filter_map(|n| n.as_str().ok())
|
self.iter_by_oid(&oid::OID_PKCS9_EMAIL_ADDRESS).filter_map(|n| n.as_str().ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if `self` has no data.
|
||||||
|
///
|
||||||
|
/// When this is the case for a `subject()`, the subject data can be found
|
||||||
|
/// in the `subjectAlt` [`extension()`](Certificate::extensions()).
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
/// use rocket::mtls::Certificate;
|
||||||
|
///
|
||||||
|
/// #[get("/auth")]
|
||||||
|
/// fn auth(cert: Certificate<'_>) {
|
||||||
|
/// let no_data = cert.subject().is_empty();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.0.as_raw().is_empty()
|
self.0.as_raw().is_empty()
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ all-features = true
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
tls = ["rocket_http/tls"]
|
tls = ["rocket_http/tls"]
|
||||||
|
mtls = ["rocket_http/mtls", "tls"]
|
||||||
secrets = ["rocket_http/private-cookies"]
|
secrets = ["rocket_http/private-cookies"]
|
||||||
json = ["serde_json", "tokio/io-util"]
|
json = ["serde_json", "tokio/io-util"]
|
||||||
msgpack = ["rmp-serde", "tokio/io-util"]
|
msgpack = ["rmp-serde", "tokio/io-util"]
|
||||||
|
|
|
@ -70,13 +70,17 @@ pub struct Config {
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
/// Number of threads to use for executing futures. **(default: `num_cores`)**
|
/// Number of threads to use for executing futures. **(default: `num_cores`)**
|
||||||
pub workers: usize,
|
pub workers: usize,
|
||||||
/// Keep-alive timeout in seconds; disabled when `0`. **(default: `5`)**
|
|
||||||
pub keep_alive: u32,
|
|
||||||
/// Streaming read size limits. **(default: [`Limits::default()`])**
|
|
||||||
pub limits: Limits,
|
|
||||||
/// How, if at all, to identify the server via the `Server` header.
|
/// How, if at all, to identify the server via the `Server` header.
|
||||||
/// **(default: `"Rocket"`)**
|
/// **(default: `"Rocket"`)**
|
||||||
pub ident: Ident,
|
pub ident: Ident,
|
||||||
|
/// Streaming read size limits. **(default: [`Limits::default()`])**
|
||||||
|
pub limits: Limits,
|
||||||
|
/// Directory to store temporary files in. **(default:
|
||||||
|
/// [`std::env::temp_dir()`])**
|
||||||
|
#[serde(serialize_with = "RelativePathBuf::serialize_relative")]
|
||||||
|
pub temp_dir: RelativePathBuf,
|
||||||
|
/// Keep-alive timeout in seconds; disabled when `0`. **(default: `5`)**
|
||||||
|
pub keep_alive: u32,
|
||||||
/// The TLS configuration, if any. **(default: `None`)**
|
/// The TLS configuration, if any. **(default: `None`)**
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
#[cfg_attr(nightly, doc(cfg(feature = "tls")))]
|
#[cfg_attr(nightly, doc(cfg(feature = "tls")))]
|
||||||
|
@ -89,14 +93,10 @@ pub struct Config {
|
||||||
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
||||||
#[serde(serialize_with = "SecretKey::serialize_zero")]
|
#[serde(serialize_with = "SecretKey::serialize_zero")]
|
||||||
pub secret_key: SecretKey,
|
pub secret_key: SecretKey,
|
||||||
/// Directory to store temporary files in. **(default:
|
|
||||||
/// [`std::env::temp_dir()`])**
|
|
||||||
#[serde(serialize_with = "RelativePathBuf::serialize_relative")]
|
|
||||||
pub temp_dir: RelativePathBuf,
|
|
||||||
/// Max level to log. **(default: _debug_ `normal` / _release_ `critical`)**
|
|
||||||
pub log_level: LogLevel,
|
|
||||||
/// Graceful shutdown configuration. **(default: [`Shutdown::default()`])**
|
/// Graceful shutdown configuration. **(default: [`Shutdown::default()`])**
|
||||||
pub shutdown: Shutdown,
|
pub shutdown: Shutdown,
|
||||||
|
/// Max level to log. **(default: _debug_ `normal` / _release_ `critical`)**
|
||||||
|
pub log_level: LogLevel,
|
||||||
/// Whether to use colors and emoji when logging. **(default: `true`)**
|
/// Whether to use colors and emoji when logging. **(default: `true`)**
|
||||||
#[serde(deserialize_with = "figment::util::bool_from_str_or_int")]
|
#[serde(deserialize_with = "figment::util::bool_from_str_or_int")]
|
||||||
pub cli_colors: bool,
|
pub cli_colors: bool,
|
||||||
|
@ -167,16 +167,16 @@ impl Config {
|
||||||
address: Ipv4Addr::new(127, 0, 0, 1).into(),
|
address: Ipv4Addr::new(127, 0, 0, 1).into(),
|
||||||
port: 8000,
|
port: 8000,
|
||||||
workers: num_cpus::get(),
|
workers: num_cpus::get(),
|
||||||
keep_alive: 5,
|
|
||||||
limits: Limits::default(),
|
|
||||||
ident: Ident::default(),
|
ident: Ident::default(),
|
||||||
|
limits: Limits::default(),
|
||||||
|
temp_dir: std::env::temp_dir().into(),
|
||||||
|
keep_alive: 5,
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
tls: None,
|
tls: None,
|
||||||
#[cfg(feature = "secrets")]
|
#[cfg(feature = "secrets")]
|
||||||
secret_key: SecretKey::zero(),
|
secret_key: SecretKey::zero(),
|
||||||
temp_dir: std::env::temp_dir().into(),
|
|
||||||
log_level: LogLevel::Normal,
|
|
||||||
shutdown: Shutdown::default(),
|
shutdown: Shutdown::default(),
|
||||||
|
log_level: LogLevel::Normal,
|
||||||
cli_colors: true,
|
cli_colors: true,
|
||||||
__non_exhaustive: (),
|
__non_exhaustive: (),
|
||||||
}
|
}
|
||||||
|
@ -316,31 +316,62 @@ impl Config {
|
||||||
#[cfg(not(feature = "tls"))] { false }
|
#[cfg(not(feature = "tls"))] { false }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if mTLS is enabled.
|
||||||
|
///
|
||||||
|
/// mTLS is enabled when TLS is enabled ([`Config::tls_enabled()`]) _and_
|
||||||
|
/// the `mtls` feature is enabled _and_ mTLS has been configured with a CA
|
||||||
|
/// certificate chain.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// let config = rocket::Config::default();
|
||||||
|
/// if config.mtls_enabled() {
|
||||||
|
/// println!("mTLS is enabled!");
|
||||||
|
/// } else {
|
||||||
|
/// println!("mTLS is disabled.");
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn mtls_enabled(&self) -> bool {
|
||||||
|
if !self.tls_enabled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "mtls")] {
|
||||||
|
self.tls.as_ref().map_or(false, |tls| tls.mutual.is_some())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "mtls"))] { false }
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn pretty_print(&self, figment: &Figment) {
|
pub(crate) fn pretty_print(&self, figment: &Figment) {
|
||||||
use crate::log::PaintExt;
|
use crate::log::PaintExt;
|
||||||
|
|
||||||
launch_info!("{}Configured for {}.", Paint::emoji("🔧 "), self.profile);
|
fn bold<T: std::fmt::Display>(val: T) -> Paint<T> {
|
||||||
|
Paint::default(val).bold()
|
||||||
launch_info_!("address: {}", Paint::default(&self.address).bold());
|
|
||||||
launch_info_!("port: {}", Paint::default(&self.port).bold());
|
|
||||||
launch_info_!("workers: {}", Paint::default(self.workers).bold());
|
|
||||||
launch_info_!("ident: {}", Paint::default(&self.ident).bold());
|
|
||||||
|
|
||||||
let ka = self.keep_alive;
|
|
||||||
if ka > 0 {
|
|
||||||
launch_info_!("keep-alive: {}", Paint::default(format!("{}s", ka)).bold());
|
|
||||||
} else {
|
|
||||||
launch_info_!("keep-alive: {}", Paint::default("disabled").bold());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
launch_info_!("limits: {}", Paint::default(&self.limits).bold());
|
launch_info!("{}Configured for {}.", Paint::emoji("🔧 "), self.profile);
|
||||||
match self.tls_enabled() {
|
launch_info_!("address: {}", bold(&self.address));
|
||||||
true => launch_info_!("tls: {}", Paint::default("enabled").bold()),
|
launch_info_!("port: {}", bold(&self.port));
|
||||||
false => launch_info_!("tls: {}", Paint::default("disabled").bold()),
|
launch_info_!("workers: {}", bold(self.workers));
|
||||||
|
launch_info_!("ident: {}", bold(&self.ident));
|
||||||
|
launch_info_!("limits: {}", bold(&self.limits));
|
||||||
|
launch_info_!("temp dir: {}", bold(&self.temp_dir.relative().display()));
|
||||||
|
|
||||||
|
match self.keep_alive {
|
||||||
|
0 => launch_info_!("keep-alive: {}", bold("disabled")),
|
||||||
|
ka => launch_info_!("keep-alive: {}{}", bold(ka), bold("s")),
|
||||||
|
}
|
||||||
|
|
||||||
|
match (self.tls_enabled(), self.mtls_enabled()) {
|
||||||
|
(true, true) => launch_info_!("tls: {}", bold("enabled w/mtls")),
|
||||||
|
(true, false) => launch_info_!("tls: {} w/o mtls", bold("enabled")),
|
||||||
|
(false, _) => launch_info_!("tls: {}", bold("disabled")),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "secrets")] {
|
#[cfg(feature = "secrets")] {
|
||||||
launch_info_!("secret key: {:?}", Paint::default(&self.secret_key).bold());
|
launch_info_!("secret key: {}", bold(&self.secret_key));
|
||||||
if !self.secret_key.is_provided() {
|
if !self.secret_key.is_provided() {
|
||||||
warn!("secrets enabled without a stable `secret_key`");
|
warn!("secrets enabled without a stable `secret_key`");
|
||||||
launch_info_!("disable `secrets` feature or configure a `secret_key`");
|
launch_info_!("disable `secrets` feature or configure a `secret_key`");
|
||||||
|
@ -348,10 +379,9 @@ impl Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
launch_info_!("temp dir: {}", Paint::default(&self.temp_dir.relative().display()).bold());
|
launch_info_!("shutdown: {}", bold(&self.shutdown));
|
||||||
launch_info_!("log level: {}", Paint::default(self.log_level).bold());
|
launch_info_!("log level: {}", bold(self.log_level));
|
||||||
launch_info_!("cli colors: {}", Paint::default(&self.cli_colors).bold());
|
launch_info_!("cli colors: {}", bold(&self.cli_colors));
|
||||||
launch_info_!("shutdown: {}", Paint::default(&self.shutdown).bold());
|
|
||||||
|
|
||||||
// Check for now depreacted config values.
|
// Check for now depreacted config values.
|
||||||
for (key, replacement) in Self::DEPRECATED_KEYS {
|
for (key, replacement) in Self::DEPRECATED_KEYS {
|
||||||
|
@ -399,7 +429,6 @@ impl Config {
|
||||||
/// The default profile: "debug" on `debug`, "release" on `release`.
|
/// The default profile: "debug" on `debug`, "release" on `release`.
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
pub const DEFAULT_PROFILE: Profile = Self::RELEASE_PROFILE;
|
pub const DEFAULT_PROFILE: Profile = Self::RELEASE_PROFILE;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Associated constants for stringy versions of configuration parameters.
|
/// Associated constants for stringy versions of configuration parameters.
|
||||||
|
|
|
@ -131,6 +131,9 @@ pub use ident::Ident;
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
pub use tls::{TlsConfig, CipherSuite};
|
pub use tls::{TlsConfig, CipherSuite};
|
||||||
|
|
||||||
|
#[cfg(feature = "mtls")]
|
||||||
|
pub use tls::MutualTls;
|
||||||
|
|
||||||
#[cfg(feature = "secrets")]
|
#[cfg(feature = "secrets")]
|
||||||
pub use secret_key::SecretKey;
|
pub use secret_key::SecretKey;
|
||||||
|
|
||||||
|
@ -423,6 +426,70 @@ mod tests {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "mtls")]
|
||||||
|
fn test_mtls_config() {
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
figment::Jail::expect_with(|jail| {
|
||||||
|
jail.create_file("Rocket.toml", r#"
|
||||||
|
[default.tls]
|
||||||
|
certs = "/ssl/cert.pem"
|
||||||
|
key = "/ssl/key.pem"
|
||||||
|
"#)?;
|
||||||
|
|
||||||
|
let config = Config::from(Config::figment());
|
||||||
|
assert!(config.tls.is_some());
|
||||||
|
assert!(config.tls.as_ref().unwrap().mutual.is_none());
|
||||||
|
assert!(config.tls_enabled());
|
||||||
|
assert!(!config.mtls_enabled());
|
||||||
|
|
||||||
|
jail.create_file("Rocket.toml", r#"
|
||||||
|
[default.tls]
|
||||||
|
certs = "/ssl/cert.pem"
|
||||||
|
key = "/ssl/key.pem"
|
||||||
|
mutual = { ca_certs = "/ssl/ca.pem" }
|
||||||
|
"#)?;
|
||||||
|
|
||||||
|
let config = Config::from(Config::figment());
|
||||||
|
assert!(config.tls_enabled());
|
||||||
|
assert!(config.mtls_enabled());
|
||||||
|
|
||||||
|
let mtls = config.tls.as_ref().unwrap().mutual.as_ref().unwrap();
|
||||||
|
assert_eq!(mtls.ca_certs().unwrap_left(), Path::new("/ssl/ca.pem"));
|
||||||
|
assert!(!mtls.mandatory);
|
||||||
|
|
||||||
|
jail.create_file("Rocket.toml", r#"
|
||||||
|
[default.tls]
|
||||||
|
certs = "/ssl/cert.pem"
|
||||||
|
key = "/ssl/key.pem"
|
||||||
|
|
||||||
|
[default.tls.mutual]
|
||||||
|
ca_certs = "/ssl/ca.pem"
|
||||||
|
mandatory = true
|
||||||
|
"#)?;
|
||||||
|
|
||||||
|
let config = Config::from(Config::figment());
|
||||||
|
let mtls = config.tls.as_ref().unwrap().mutual.as_ref().unwrap();
|
||||||
|
assert_eq!(mtls.ca_certs().unwrap_left(), Path::new("/ssl/ca.pem"));
|
||||||
|
assert!(mtls.mandatory);
|
||||||
|
|
||||||
|
jail.create_file("Rocket.toml", r#"
|
||||||
|
[default.tls]
|
||||||
|
certs = "/ssl/cert.pem"
|
||||||
|
key = "/ssl/key.pem"
|
||||||
|
mutual = { ca_certs = "relative/ca.pem" }
|
||||||
|
"#)?;
|
||||||
|
|
||||||
|
let config = Config::from(Config::figment());
|
||||||
|
let mtls = config.tls.as_ref().unwrap().mutual().unwrap();
|
||||||
|
assert_eq!(mtls.ca_certs().unwrap_left(),
|
||||||
|
jail.directory().join("relative/ca.pem"));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_profiles_merge() {
|
fn test_profiles_merge() {
|
||||||
figment::Jail::expect_with(|jail| {
|
figment::Jail::expect_with(|jail| {
|
||||||
|
|
|
@ -246,7 +246,7 @@ impl<'de> Deserialize<'de> for SecretKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for SecretKey {
|
impl fmt::Display for SecretKey {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
if self.is_zero() {
|
if self.is_zero() {
|
||||||
f.write_str("[zero]")
|
f.write_str("[zero]")
|
||||||
|
@ -258,3 +258,9 @@ impl fmt::Debug for SecretKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for SecretKey {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
<Self as fmt::Display>::fmt(self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -26,44 +26,63 @@ use indexmap::IndexSet;
|
||||||
/// ciphersuite preferences over the client's. The default and recommended
|
/// ciphersuite preferences over the client's. The default and recommended
|
||||||
/// value is `false`.
|
/// value is `false`.
|
||||||
///
|
///
|
||||||
/// The following example illustrates manual configuration:
|
/// Additionally, the `mutual` parameter controls if and how the server
|
||||||
|
/// authenticates clients via mutual TLS. It works in concert with the
|
||||||
|
/// [`mtls`](crate::mtls) module. See [`MutualTls`] for configuration details.
|
||||||
|
///
|
||||||
|
/// In `Rocket.toml`, configuration might look like:
|
||||||
|
///
|
||||||
|
/// ```toml
|
||||||
|
/// [default.tls]
|
||||||
|
/// certs = "private/rsa_sha256_cert.pem"
|
||||||
|
/// key = "private/rsa_sha256_key.pem"
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// With a custom programmatic configuration, this might look like:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::config::{Config, TlsConfig, CipherSuite};
|
/// use rocket::config::{Config, TlsConfig, CipherSuite};
|
||||||
///
|
///
|
||||||
/// // From a manually constructed figment.
|
/// #[launch]
|
||||||
/// let figment = rocket::Config::figment()
|
/// fn rocket() -> _ {
|
||||||
|
/// let tls_config = TlsConfig::from_paths("/ssl/certs.pem", "/ssl/key.pem")
|
||||||
|
/// .with_ciphers(CipherSuite::TLS_V13_SET)
|
||||||
|
/// .with_preferred_server_cipher_order(true);
|
||||||
|
///
|
||||||
|
/// let config = Config {
|
||||||
|
/// tls: Some(tls_config),
|
||||||
|
/// ..Default::default()
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// rocket::custom(config)
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Or by creating a custom figment:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use rocket::config::Config;
|
||||||
|
///
|
||||||
|
/// let figment = Config::figment()
|
||||||
/// .merge(("tls.certs", "path/to/certs.pem"))
|
/// .merge(("tls.certs", "path/to/certs.pem"))
|
||||||
/// .merge(("tls.key", vec![0; 32]));
|
/// .merge(("tls.key", vec![0; 32]));
|
||||||
///
|
/// #
|
||||||
/// let config = rocket::Config::from(figment);
|
/// # let config = rocket::Config::from(figment);
|
||||||
/// let tls_config = config.tls.as_ref().unwrap();
|
/// # let tls_config = config.tls.as_ref().unwrap();
|
||||||
/// assert!(tls_config.certs().is_left());
|
/// # assert!(tls_config.certs().is_left());
|
||||||
/// assert!(tls_config.key().is_right());
|
/// # assert!(tls_config.key().is_right());
|
||||||
/// assert_eq!(tls_config.ciphers().count(), 9);
|
/// # assert_eq!(tls_config.ciphers().count(), 9);
|
||||||
/// assert!(!tls_config.prefer_server_cipher_order());
|
/// # assert!(!tls_config.prefer_server_cipher_order());
|
||||||
///
|
|
||||||
/// // From a serialized `TlsConfig`.
|
|
||||||
/// let tls_config = TlsConfig::from_paths("/ssl/certs.pem", "/ssl/key.pem")
|
|
||||||
/// .with_ciphers(CipherSuite::TLS_V13_SET)
|
|
||||||
/// .with_preferred_server_cipher_order(true);
|
|
||||||
///
|
|
||||||
/// let figment = rocket::Config::figment()
|
|
||||||
/// .merge(("tls", tls_config));
|
|
||||||
///
|
|
||||||
/// let config = rocket::Config::from(figment);
|
|
||||||
/// let tls_config = config.tls.as_ref().unwrap();
|
|
||||||
/// assert_eq!(tls_config.ciphers().count(), 3);
|
|
||||||
/// assert!(tls_config.prefer_server_cipher_order());
|
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(PartialEq, Debug, Clone, Deserialize, Serialize)]
|
#[derive(PartialEq, Debug, Clone, Deserialize, Serialize)]
|
||||||
#[cfg_attr(nightly, doc(cfg(feature = "tls")))]
|
#[cfg_attr(nightly, doc(cfg(feature = "tls")))]
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
pub struct TlsConfig {
|
pub struct TlsConfig {
|
||||||
/// Path or raw bytes for the DER-encoded X.509 TLS certificate chain.
|
/// Path to a PEM file with, or raw bytes for, a DER-encoded X.509 TLS
|
||||||
|
/// certificate chain.
|
||||||
pub(crate) certs: Either<RelativePathBuf, Vec<u8>>,
|
pub(crate) certs: Either<RelativePathBuf, Vec<u8>>,
|
||||||
/// Path or raw bytes to DER-encoded ASN.1 key in either PKCS#8 or PKCS#1
|
/// Path to a PEM file with, or raw bytes for, DER-encoded private key in
|
||||||
/// format.
|
/// either PKCS#8 or PKCS#1 format.
|
||||||
pub(crate) key: Either<RelativePathBuf, Vec<u8>>,
|
pub(crate) key: Either<RelativePathBuf, Vec<u8>>,
|
||||||
/// List of TLS cipher suites in server-preferred order.
|
/// List of TLS cipher suites in server-preferred order.
|
||||||
#[serde(default = "CipherSuite::default_set")]
|
#[serde(default = "CipherSuite::default_set")]
|
||||||
|
@ -78,11 +97,77 @@ pub struct TlsConfig {
|
||||||
pub(crate) mutual: Option<MutualTls>,
|
pub(crate) mutual: Option<MutualTls>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mutual TLS configuration.
|
||||||
|
///
|
||||||
|
/// Configuration works in concert with the [`mtls`](crate::mtls) module, which
|
||||||
|
/// provides a request guard to validate, verify, and retrieve client
|
||||||
|
/// certificates in routes.
|
||||||
|
///
|
||||||
|
/// By default, mutual TLS is disabled and client certificates are not required,
|
||||||
|
/// validated or verified. To enable mutual TLS, the `mtls` feature must be
|
||||||
|
/// enabled and support configured via two `tls.mutual` parameters:
|
||||||
|
///
|
||||||
|
/// * `ca_certs`
|
||||||
|
///
|
||||||
|
/// A required path to a PEM file or raw bytes to a DER-encoded X.509 TLS
|
||||||
|
/// certificate chain for the certificate authority to verify client
|
||||||
|
/// certificates against. When a path is configured in a file, such as
|
||||||
|
/// `Rocket.toml`, relative paths are interpreted as relative to the source
|
||||||
|
/// file's directory.
|
||||||
|
///
|
||||||
|
/// * `mandatory`
|
||||||
|
///
|
||||||
|
/// An optional boolean that control whether client authentication is
|
||||||
|
/// required.
|
||||||
|
///
|
||||||
|
/// When `true`, client authentication is required. TLS connections where
|
||||||
|
/// the client does not present a certificate are immediately terminated.
|
||||||
|
/// When `false`, the client is not required to present a certificate. In
|
||||||
|
/// either case, if a certificate _is_ presented, it must be valid or the
|
||||||
|
/// connection is terminated.
|
||||||
|
///
|
||||||
|
/// In a `Rocket.toml`, configuration might look like:
|
||||||
|
///
|
||||||
|
/// ```toml
|
||||||
|
/// [default.tls.mutual]
|
||||||
|
/// ca_certs = "/ssl/ca_cert.pem"
|
||||||
|
/// mandatory = true # when absent, defaults to false
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Programmatically, configuration might look like:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
/// use rocket::config::{Config, TlsConfig, MutualTls};
|
||||||
|
///
|
||||||
|
/// #[launch]
|
||||||
|
/// fn rocket() -> _ {
|
||||||
|
/// let tls_config = TlsConfig::from_paths("/ssl/certs.pem", "/ssl/key.pem")
|
||||||
|
/// .with_mutual(MutualTls::from_path("/ssl/ca_cert.pem"));
|
||||||
|
///
|
||||||
|
/// let config = Config {
|
||||||
|
/// tls: Some(tls_config),
|
||||||
|
/// ..Default::default()
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// rocket::custom(config)
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
#[derive(PartialEq, Debug, Clone, Deserialize, Serialize)]
|
#[derive(PartialEq, Debug, Clone, Deserialize, Serialize)]
|
||||||
#[cfg(feature = "mtls")]
|
#[cfg(feature = "mtls")]
|
||||||
#[cfg_attr(nightly, doc(cfg(feature = "mtls")))]
|
#[cfg_attr(nightly, doc(cfg(feature = "mtls")))]
|
||||||
pub struct MutualTls {
|
pub struct MutualTls {
|
||||||
|
/// Path to a PEM file with, or raw bytes for, DER-encoded Certificate
|
||||||
|
/// Authority certificates which will be used to verify client-presented
|
||||||
|
/// certificates.
|
||||||
|
// TODO: We should support more than one root.
|
||||||
pub(crate) ca_certs: Either<RelativePathBuf, Vec<u8>>,
|
pub(crate) ca_certs: Either<RelativePathBuf, Vec<u8>>,
|
||||||
|
/// Whether the client is required to present a certificate.
|
||||||
|
///
|
||||||
|
/// When `true`, the client is required to present a valid certificate to
|
||||||
|
/// proceed with TLS. When `false`, the client is not required to present a
|
||||||
|
/// certificate. In either case, if a certificate _is_ presented, it must be
|
||||||
|
/// valid or the connection is terminated.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[serde(deserialize_with = "figment::util::bool_from_str_or_int")]
|
#[serde(deserialize_with = "figment::util::bool_from_str_or_int")]
|
||||||
pub mandatory: bool,
|
pub mandatory: bool,
|
||||||
|
|
|
@ -52,7 +52,7 @@ pub struct StreamReader<'r> {
|
||||||
/// The current state of `StreamReader` `AsyncRead` adapter.
|
/// The current state of `StreamReader` `AsyncRead` adapter.
|
||||||
enum State {
|
enum State {
|
||||||
Pending,
|
Pending,
|
||||||
Partial(Cursor<hyper::Bytes>),
|
Partial(Cursor<hyper::body::Bytes>),
|
||||||
Done,
|
Done,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,7 +257,7 @@ impl AsyncRead for DataStream<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stream for StreamKind<'_> {
|
impl Stream for StreamKind<'_> {
|
||||||
type Item = io::Result<hyper::Bytes>;
|
type Item = io::Result<hyper::body::Bytes>;
|
||||||
|
|
||||||
fn poll_next(
|
fn poll_next(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
|
|
|
@ -63,6 +63,7 @@
|
||||||
//! |-----------|---------------------------------------------------------|
|
//! |-----------|---------------------------------------------------------|
|
||||||
//! | `secrets` | Support for authenticated, encrypted [private cookies]. |
|
//! | `secrets` | Support for authenticated, encrypted [private cookies]. |
|
||||||
//! | `tls` | Support for [TLS] encrypted connections. |
|
//! | `tls` | Support for [TLS] encrypted connections. |
|
||||||
|
//! | `mtls` | Support for verified clients via [mutual TLS]. |
|
||||||
//! | `json` | Support for [JSON (de)serialization]. |
|
//! | `json` | Support for [JSON (de)serialization]. |
|
||||||
//! | `msgpack` | Support for [MessagePack (de)serialization]. |
|
//! | `msgpack` | Support for [MessagePack (de)serialization]. |
|
||||||
//! | `uuid` | Support for [UUID value parsing and (de)serialization]. |
|
//! | `uuid` | Support for [UUID value parsing and (de)serialization]. |
|
||||||
|
@ -79,6 +80,7 @@
|
||||||
//! [UUID value parsing and (de)serialization]: crate::serde::uuid
|
//! [UUID value parsing and (de)serialization]: crate::serde::uuid
|
||||||
//! [private cookies]: https://rocket.rs/v0.5-rc/guide/requests/#private-cookies
|
//! [private cookies]: https://rocket.rs/v0.5-rc/guide/requests/#private-cookies
|
||||||
//! [TLS]: https://rocket.rs/v0.5-rc/guide/configuration/#tls
|
//! [TLS]: https://rocket.rs/v0.5-rc/guide/configuration/#tls
|
||||||
|
//! [mutual TLS]: crate::mtls
|
||||||
//!
|
//!
|
||||||
//! ## Configuration
|
//! ## Configuration
|
||||||
//!
|
//!
|
||||||
|
@ -137,6 +139,10 @@ pub mod http {
|
||||||
pub use crate::cookies::*;
|
pub use crate::cookies::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "mtls")]
|
||||||
|
#[cfg_attr(nightly, doc(cfg(feature = "mtls")))]
|
||||||
|
pub mod mtls;
|
||||||
|
|
||||||
/// TODO: We need a futures mod or something.
|
/// TODO: We need a futures mod or something.
|
||||||
mod trip_wire;
|
mod trip_wire;
|
||||||
mod shutdown;
|
mod shutdown;
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
//! Support for mutual TLS client certificates.
|
||||||
|
//!
|
||||||
|
//! For details on how to configure mutual TLS, see
|
||||||
|
//! [`MutualTls`](crate::config::MutualTls) and the [TLS
|
||||||
|
//! guide](https://rocket.rs/v0.5-rc/guide/configuration/#tls). See
|
||||||
|
//! [`Certificate`] for a request guard that validated, verifies, and retrieves
|
||||||
|
//! client certificates.
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use crate::http::tls::mtls::*;
|
||||||
|
|
||||||
|
use crate::request::{Request, FromRequest, Outcome};
|
||||||
|
use crate::outcome::{try_outcome, IntoOutcome};
|
||||||
|
use crate::http::Status;
|
||||||
|
|
||||||
|
#[crate::async_trait]
|
||||||
|
impl<'r> FromRequest<'r> for Certificate<'r> {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||||
|
let certs = try_outcome!(req.connection.client_certificates.as_ref().or_forward(()));
|
||||||
|
Certificate::parse(certs).into_outcome(Status::Unauthorized)
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,8 @@ pub use self::from_param::{FromParam, FromSegments};
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use crate::response::flash::FlashMessage;
|
pub use crate::response::flash::FlashMessage;
|
||||||
|
|
||||||
|
pub(crate) use self::request::ConnectionMeta;
|
||||||
|
|
||||||
crate::export! {
|
crate::export! {
|
||||||
/// Store and immediately retrieve a value `$v` in `$request`'s local cache
|
/// Store and immediately retrieve a value `$v` in `$request`'s local cache
|
||||||
/// using a locally generated anonymous type to avoid type conflicts.
|
/// using a locally generated anonymous type to avoid type conflicts.
|
||||||
|
|
|
@ -8,16 +8,16 @@ use state::{Container, Storage};
|
||||||
use futures::future::BoxFuture;
|
use futures::future::BoxFuture;
|
||||||
use atomic::{Atomic, Ordering};
|
use atomic::{Atomic, Ordering};
|
||||||
|
|
||||||
// use crate::request::{FromParam, FromSegments, FromRequest, Outcome};
|
use crate::{Rocket, Route, Orbit};
|
||||||
use crate::request::{FromParam, FromSegments, FromRequest, Outcome};
|
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::{Rocket, Route, Orbit};
|
|
||||||
use crate::http::uri::{fmt::Path, Origin, Segments, Host, Authority};
|
|
||||||
use crate::http::{hyper, Method, Header, HeaderMap};
|
use crate::http::{hyper, Method, Header, HeaderMap};
|
||||||
use crate::http::{ContentType, Accept, MediaType, CookieJar, Cookie};
|
use crate::http::{ContentType, Accept, MediaType, CookieJar, Cookie};
|
||||||
use crate::http::uncased::UncasedStr;
|
use crate::http::uncased::UncasedStr;
|
||||||
use crate::data::Limits;
|
use crate::http::private::RawCertificate;
|
||||||
|
use crate::http::uri::{fmt::Path, Origin, Segments, Host, Authority};
|
||||||
|
|
||||||
/// The type of an incoming web request.
|
/// The type of an incoming web request.
|
||||||
///
|
///
|
||||||
|
@ -28,12 +28,19 @@ use crate::data::Limits;
|
||||||
pub struct Request<'r> {
|
pub struct Request<'r> {
|
||||||
method: Atomic<Method>,
|
method: Atomic<Method>,
|
||||||
uri: Origin<'r>,
|
uri: Origin<'r>,
|
||||||
host: Option<Host<'r>>,
|
|
||||||
headers: HeaderMap<'r>,
|
headers: HeaderMap<'r>,
|
||||||
remote: Option<SocketAddr>,
|
pub(crate) connection: ConnectionMeta,
|
||||||
pub(crate) state: RequestState<'r>,
|
pub(crate) state: RequestState<'r>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Information derived from an incoming connection, if any.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct ConnectionMeta {
|
||||||
|
pub remote: Option<SocketAddr>,
|
||||||
|
pub client_certificates: Option<Arc<Vec<RawCertificate>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information derived from the request.
|
||||||
pub(crate) struct RequestState<'r> {
|
pub(crate) struct RequestState<'r> {
|
||||||
pub rocket: &'r Rocket<Orbit>,
|
pub rocket: &'r Rocket<Orbit>,
|
||||||
pub route: Atomic<Option<&'r Route>>,
|
pub route: Atomic<Option<&'r Route>>,
|
||||||
|
@ -41,6 +48,7 @@ pub(crate) struct RequestState<'r> {
|
||||||
pub accept: Storage<Option<Accept>>,
|
pub accept: Storage<Option<Accept>>,
|
||||||
pub content_type: Storage<Option<ContentType>>,
|
pub content_type: Storage<Option<ContentType>>,
|
||||||
pub cache: Arc<Container![Send + Sync]>,
|
pub cache: Arc<Container![Send + Sync]>,
|
||||||
|
pub host: Option<Host<'r>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Request<'_> {
|
impl Request<'_> {
|
||||||
|
@ -48,9 +56,8 @@ impl Request<'_> {
|
||||||
Request {
|
Request {
|
||||||
method: Atomic::new(self.method()),
|
method: Atomic::new(self.method()),
|
||||||
uri: self.uri.clone(),
|
uri: self.uri.clone(),
|
||||||
host: self.host.clone(),
|
|
||||||
headers: self.headers.clone(),
|
headers: self.headers.clone(),
|
||||||
remote: self.remote,
|
connection: self.connection.clone(),
|
||||||
state: self.state.clone(),
|
state: self.state.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,6 +72,7 @@ impl RequestState<'_> {
|
||||||
accept: self.accept.clone(),
|
accept: self.accept.clone(),
|
||||||
content_type: self.content_type.clone(),
|
content_type: self.content_type.clone(),
|
||||||
cache: self.cache.clone(),
|
cache: self.cache.clone(),
|
||||||
|
host: self.host.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,10 +87,12 @@ impl<'r> Request<'r> {
|
||||||
) -> Request<'r> {
|
) -> Request<'r> {
|
||||||
Request {
|
Request {
|
||||||
uri,
|
uri,
|
||||||
host: None,
|
|
||||||
method: Atomic::new(method),
|
method: Atomic::new(method),
|
||||||
headers: HeaderMap::new(),
|
headers: HeaderMap::new(),
|
||||||
remote: None,
|
connection: ConnectionMeta {
|
||||||
|
remote: None,
|
||||||
|
client_certificates: None,
|
||||||
|
},
|
||||||
state: RequestState {
|
state: RequestState {
|
||||||
rocket,
|
rocket,
|
||||||
route: Atomic::new(None),
|
route: Atomic::new(None),
|
||||||
|
@ -90,6 +100,7 @@ impl<'r> Request<'r> {
|
||||||
accept: Storage::new(),
|
accept: Storage::new(),
|
||||||
content_type: Storage::new(),
|
content_type: Storage::new(),
|
||||||
cache: Arc::new(<Container![Send + Sync]>::new()),
|
cache: Arc::new(<Container![Send + Sync]>::new()),
|
||||||
|
host: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -179,6 +190,10 @@ impl<'r> Request<'r> {
|
||||||
/// component. Otherwise, this method returns the contents of the
|
/// component. Otherwise, this method returns the contents of the
|
||||||
/// `:authority` pseudo-header request field.
|
/// `:authority` pseudo-header request field.
|
||||||
///
|
///
|
||||||
|
/// Note that this method _only_ reflects the `HOST` header in the _initial_
|
||||||
|
/// request and not any changes made thereafter. To change the value
|
||||||
|
/// returned by this method, use [`Request::set_host()`].
|
||||||
|
///
|
||||||
/// # ⚠️ DANGER ⚠️
|
/// # ⚠️ DANGER ⚠️
|
||||||
///
|
///
|
||||||
/// Using the user-controlled `host` to construct URLs is a security hazard!
|
/// Using the user-controlled `host` to construct URLs is a security hazard!
|
||||||
|
@ -257,7 +272,7 @@ impl<'r> Request<'r> {
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn host(&self) -> Option<&Host<'r>> {
|
pub fn host(&self) -> Option<&Host<'r>> {
|
||||||
self.host.as_ref()
|
self.state.host.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the host of `self` to `host`.
|
/// Sets the host of `self` to `host`.
|
||||||
|
@ -282,7 +297,7 @@ impl<'r> Request<'r> {
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn set_host(&mut self, host: Host<'r>) {
|
pub fn set_host(&mut self, host: Host<'r>) {
|
||||||
self.host = Some(host);
|
self.state.host = Some(host);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the raw address of the remote connection that initiated this
|
/// Returns the raw address of the remote connection that initiated this
|
||||||
|
@ -314,7 +329,7 @@ impl<'r> Request<'r> {
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn remote(&self) -> Option<SocketAddr> {
|
pub fn remote(&self) -> Option<SocketAddr> {
|
||||||
self.remote
|
self.connection.remote
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the remote address of `self` to `address`.
|
/// Sets the remote address of `self` to `address`.
|
||||||
|
@ -337,7 +352,7 @@ impl<'r> Request<'r> {
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn set_remote(&mut self, address: SocketAddr) {
|
pub fn set_remote(&mut self, address: SocketAddr) {
|
||||||
self.remote = Some(address);
|
self.connection.remote = Some(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the IP address in the "X-Real-IP" header of the request if such
|
/// Returns the IP address in the "X-Real-IP" header of the request if such
|
||||||
|
@ -949,8 +964,8 @@ impl<'r> Request<'r> {
|
||||||
/// Convert from Hyper types into a Rocket Request.
|
/// Convert from Hyper types into a Rocket Request.
|
||||||
pub(crate) fn from_hyp(
|
pub(crate) fn from_hyp(
|
||||||
rocket: &'r Rocket<Orbit>,
|
rocket: &'r Rocket<Orbit>,
|
||||||
hyper: &'r hyper::RequestParts,
|
hyper: &'r hyper::request::Parts,
|
||||||
addr: SocketAddr
|
connection: Option<ConnectionMeta>,
|
||||||
) -> Result<Request<'r>, Error<'r>> {
|
) -> Result<Request<'r>, Error<'r>> {
|
||||||
// Ensure that the method is known. TODO: Allow made-up methods?
|
// Ensure that the method is known. TODO: Allow made-up methods?
|
||||||
let method = Method::from_hyp(&hyper.method)
|
let method = Method::from_hyp(&hyper.method)
|
||||||
|
@ -965,11 +980,13 @@ impl<'r> Request<'r> {
|
||||||
|
|
||||||
// Construct the request object.
|
// Construct the request object.
|
||||||
let mut request = Request::new(rocket, method, uri);
|
let mut request = Request::new(rocket, method, uri);
|
||||||
request.set_remote(addr);
|
if let Some(connection) = connection {
|
||||||
|
request.connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
// Determine the host. On HTTP < 2, use the `HOST` header. Otherwise,
|
// Determine the host. On HTTP < 2, use the `HOST` header. Otherwise,
|
||||||
// use the `:authority` pseudo-header which hyper makes part of the URI.
|
// use the `:authority` pseudo-header which hyper makes part of the URI.
|
||||||
request.host = if hyper.version < hyper::Version::HTTP_2 {
|
request.state.host = if hyper.version < hyper::Version::HTTP_2 {
|
||||||
hyper.headers.get("host").and_then(|h| Host::parse_bytes(h.as_bytes()).ok())
|
hyper.headers.get("host").and_then(|h| Host::parse_bytes(h.as_bytes()).ok())
|
||||||
} else {
|
} else {
|
||||||
hyper.uri.host().map(|h| Host::new(Authority::new(None, h, hyper.uri.port_u16())))
|
hyper.uri.host().map(|h| Host::new(Authority::new(None, h, hyper.uri.port_u16())))
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use std::net::{Ipv4Addr, SocketAddrV4};
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::Request;
|
use crate::Request;
|
||||||
|
@ -17,9 +16,8 @@ macro_rules! assert_headers {
|
||||||
|
|
||||||
// Create a valid `Rocket` and convert the hyper req to a Rocket one.
|
// Create a valid `Rocket` and convert the hyper req to a Rocket one.
|
||||||
let client = Client::debug_with(vec![]).unwrap();
|
let client = Client::debug_with(vec![]).unwrap();
|
||||||
let addr = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 8000).into();
|
|
||||||
let hyper = req.into_parts().0;
|
let hyper = req.into_parts().0;
|
||||||
let req = Request::from_hyp(client.rocket(), &hyper, addr).unwrap();
|
let req = Request::from_hyp(client.rocket(), &hyper, None).unwrap();
|
||||||
|
|
||||||
// Dispatch the request and check that the headers match.
|
// Dispatch the request and check that the headers match.
|
||||||
let actual_headers = req.headers();
|
let actual_headers = req.headers();
|
||||||
|
|
|
@ -12,6 +12,7 @@ use crate::form::Form;
|
||||||
use crate::outcome::Outcome;
|
use crate::outcome::Outcome;
|
||||||
use crate::error::{Error, ErrorKind};
|
use crate::error::{Error, ErrorKind};
|
||||||
use crate::ext::{AsyncReadExt, CancellableListener, CancellableIo};
|
use crate::ext::{AsyncReadExt, CancellableListener, CancellableIo};
|
||||||
|
use crate::request::ConnectionMeta;
|
||||||
|
|
||||||
use crate::http::{uri::Origin, hyper, Method, Status, Header};
|
use crate::http::{uri::Origin, hyper, Method, Status, Header};
|
||||||
use crate::http::private::{bind_tcp, Listener, Connection, Incoming};
|
use crate::http::private::{bind_tcp, Listener, Connection, Incoming};
|
||||||
|
@ -61,7 +62,7 @@ async fn handle<Fut, T, F>(name: Option<&str>, run: F) -> Option<T>
|
||||||
// `HyperResponse` type, this function does the actual response processing.
|
// `HyperResponse` type, this function does the actual response processing.
|
||||||
async fn hyper_service_fn(
|
async fn hyper_service_fn(
|
||||||
rocket: Arc<Rocket<Orbit>>,
|
rocket: Arc<Rocket<Orbit>>,
|
||||||
addr: std::net::SocketAddr,
|
conn: ConnectionMeta,
|
||||||
hyp_req: hyper::Request<hyper::Body>,
|
hyp_req: hyper::Request<hyper::Body>,
|
||||||
) -> Result<hyper::Response<hyper::Body>, io::Error> {
|
) -> Result<hyper::Response<hyper::Body>, io::Error> {
|
||||||
// This future must return a hyper::Response, but the response body might
|
// This future must return a hyper::Response, but the response body might
|
||||||
|
@ -72,7 +73,7 @@ async fn hyper_service_fn(
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
// Convert a Hyper request into a Rocket request.
|
// Convert a Hyper request into a Rocket request.
|
||||||
let (h_parts, mut h_body) = hyp_req.into_parts();
|
let (h_parts, mut h_body) = hyp_req.into_parts();
|
||||||
match Request::from_hyp(&rocket, &h_parts, addr) {
|
match Request::from_hyp(&rocket, &h_parts, Some(conn)) {
|
||||||
Ok(mut req) => {
|
Ok(mut req) => {
|
||||||
// Convert into Rocket `Data`, dispatch request, write response.
|
// Convert into Rocket `Data`, dispatch request, write response.
|
||||||
let mut data = Data::from(&mut h_body);
|
let mut data = Data::from(&mut h_body);
|
||||||
|
@ -366,20 +367,18 @@ impl Rocket<Orbit> {
|
||||||
.map_err(|e| Error::new(ErrorKind::Io(e)))?;
|
.map_err(|e| Error::new(ErrorKind::Io(e)))?;
|
||||||
|
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
if let Some(ref config) = self.config.tls {
|
if self.config.tls_enabled() {
|
||||||
use crate::http::tls::TlsListener;
|
if let Some(ref config) = self.config.tls {
|
||||||
|
use crate::http::tls::TlsListener;
|
||||||
|
|
||||||
let (certs, key) = config.to_readers().map_err(ErrorKind::Io)?;
|
let conf = config.to_native_config().map_err(ErrorKind::Io)?;
|
||||||
let ciphers = config.rustls_ciphers();
|
let l = TlsListener::bind(addr, conf).await.map_err(ErrorKind::Bind)?;
|
||||||
let server_order = config.prefer_server_cipher_order;
|
addr = l.local_addr().unwrap_or(addr);
|
||||||
let l = TlsListener::bind(addr, certs, key, ciphers, server_order).await
|
self.config.address = addr.ip();
|
||||||
.map_err(ErrorKind::Bind)?;
|
self.config.port = addr.port();
|
||||||
|
ready(&mut self).await;
|
||||||
addr = l.local_addr().unwrap_or(addr);
|
return self.http_server(l).await;
|
||||||
self.config.address = addr.ip();
|
}
|
||||||
self.config.port = addr.port();
|
|
||||||
ready(&mut self).await;
|
|
||||||
return self.http_server(l).await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let l = bind_tcp(addr).await.map_err(ErrorKind::Bind)?;
|
let l = bind_tcp(addr).await.map_err(ErrorKind::Bind)?;
|
||||||
|
@ -443,10 +442,14 @@ impl Rocket<Orbit> {
|
||||||
let rocket = Arc::new(self);
|
let rocket = Arc::new(self);
|
||||||
let service_fn = move |conn: &CancellableIo<_, L::Connection>| {
|
let service_fn = move |conn: &CancellableIo<_, L::Connection>| {
|
||||||
let rocket = rocket.clone();
|
let rocket = rocket.clone();
|
||||||
let remote = conn.peer_address().unwrap_or_else(|| ([0, 0, 0, 0], 0).into());
|
let connection = ConnectionMeta {
|
||||||
|
remote: conn.peer_address(),
|
||||||
|
client_certificates: conn.peer_certificates().map(Arc::new),
|
||||||
|
};
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
Ok::<_, std::convert::Infallible>(hyper::service_fn(move |req| {
|
Ok::<_, std::convert::Infallible>(hyper::service::service_fn(move |req| {
|
||||||
hyper_service_fn(rocket.clone(), remote, req)
|
hyper_service_fn(rocket.clone(), connection.clone(), req)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -457,7 +460,7 @@ impl Rocket<Orbit> {
|
||||||
.http1_keepalive(http1_keepalive)
|
.http1_keepalive(http1_keepalive)
|
||||||
.http1_preserve_header_case(true)
|
.http1_preserve_header_case(true)
|
||||||
.http2_keep_alive_interval(http2_keep_alive)
|
.http2_keep_alive_interval(http2_keep_alive)
|
||||||
.serve(hyper::make_service_fn(service_fn))
|
.serve(hyper::service::make_service_fn(service_fn))
|
||||||
.with_graceful_shutdown(shutdown.clone())
|
.with_graceful_shutdown(shutdown.clone())
|
||||||
.map_err(|e| Error::new(ErrorKind::Runtime(Box::new(e))));
|
.map_err(|e| Error::new(ErrorKind::Runtime(Box::new(e))));
|
||||||
|
|
||||||
|
|
|
@ -102,6 +102,7 @@ function test_core() {
|
||||||
FEATURES=(
|
FEATURES=(
|
||||||
secrets
|
secrets
|
||||||
tls
|
tls
|
||||||
|
mtls
|
||||||
json
|
json
|
||||||
msgpack
|
msgpack
|
||||||
uuid
|
uuid
|
||||||
|
|
|
@ -236,24 +236,8 @@ Security). To enable TLS support:
|
||||||
certs = "path/to/certs.pem" # Path or bytes to DER-encoded X.509 TLS cert chain.
|
certs = "path/to/certs.pem" # Path or bytes to DER-encoded X.509 TLS cert chain.
|
||||||
```
|
```
|
||||||
|
|
||||||
Next time the server is run, Rocket will report that TLS is enabled during
|
The `tls` parameter is expected to be a dictionary that deserializes into a
|
||||||
ignition:
|
[`TlsConfig`] structure:
|
||||||
|
|
||||||
```text
|
|
||||||
🔧 Configured for debug.
|
|
||||||
...
|
|
||||||
>> tls: enabled
|
|
||||||
```
|
|
||||||
|
|
||||||
The [TLS example](@example/tls) illustrates a fully configured TLS server.
|
|
||||||
|
|
||||||
! warning: Rocket's built-in TLS supports only TLS 1.2 and 1.3. This may not be
|
|
||||||
suitable for production use.
|
|
||||||
|
|
||||||
#### TLS Parameters
|
|
||||||
|
|
||||||
The `tls` parameter is expected to be a dictionary with at-most four keys which
|
|
||||||
deserialize into the [`TlsConfig`] structure. These are:
|
|
||||||
|
|
||||||
| key | required | type |
|
| key | required | type |
|
||||||
|------------------------------|-----------|-------------------------------------------------------|
|
|------------------------------|-----------|-------------------------------------------------------|
|
||||||
|
@ -261,9 +245,11 @@ deserialize into the [`TlsConfig`] structure. These are:
|
||||||
| `certs` | **_yes_** | Path or bytes to DER-encoded X.509 TLS cert chain. |
|
| `certs` | **_yes_** | Path or bytes to DER-encoded X.509 TLS cert chain. |
|
||||||
| `ciphers` | no | Array of [`CipherSuite`]s to enable. |
|
| `ciphers` | no | Array of [`CipherSuite`]s to enable. |
|
||||||
| `prefer_server_cipher_order` | no | Boolean for whether to [prefer server cipher suites]. |
|
| `prefer_server_cipher_order` | no | Boolean for whether to [prefer server cipher suites]. |
|
||||||
|
| `mutual` | no | A map with [mutual TLS] configuration. |
|
||||||
|
|
||||||
[`CipherSuite`]: @api/rocket/config/enum.CipherSuite.html
|
[`CipherSuite`]: @api/rocket/config/enum.CipherSuite.html
|
||||||
[prefer server cipher suites]: @api/rocket/config/struct.TlsConfig.html#method.with_preferred_server_cipher_order
|
[prefer server cipher suites]: @api/rocket/config/struct.TlsConfig.html#method.with_preferred_server_cipher_order
|
||||||
|
[mutual TLS]: #mutual-tls
|
||||||
|
|
||||||
When specified via TOML or other serialized formats, each [`CipherSuite`] is
|
When specified via TOML or other serialized formats, each [`CipherSuite`] is
|
||||||
written as a string representation of the respective variant. For example,
|
written as a string representation of the respective variant. For example,
|
||||||
|
@ -288,16 +274,69 @@ ciphers = [
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Mutual TLS
|
||||||
|
|
||||||
|
Rocket supports mutual TLS client authentication. Configuration works in concert
|
||||||
|
with the [`mtls`] module, which provides a request guard to validate, verify,
|
||||||
|
and retrieve client certificates in routes.
|
||||||
|
|
||||||
|
By default, mutual TLS is disabled and client certificates are not required,
|
||||||
|
validated or verified. To enable mutual TLS, the `mtls` feature must be
|
||||||
|
enabled and support configured via the `tls.mutual` config parameter:
|
||||||
|
|
||||||
|
1. Enable the `mtls` crate feature in `Cargo.toml`:
|
||||||
|
|
||||||
|
```toml,ignore
|
||||||
|
[dependencies]
|
||||||
|
rocket = { version = "0.5.0-rc.1", features = ["mtls"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
This implicitly enables the `tls` feature.
|
||||||
|
|
||||||
|
2. Configure a CA certificate chain via the `tls.mutual.ca_certs`
|
||||||
|
configuration parameter. With the default provider, this can be done via
|
||||||
|
`Rocket.toml` as:
|
||||||
|
|
||||||
|
```toml,ignore
|
||||||
|
[default.tls.mutual]
|
||||||
|
ca_certs = "path/to/ca_certs.pem" # Path or bytes to DER-encoded X.509 TLS cert chain.
|
||||||
|
mandatory = true # when absent, defaults to false
|
||||||
|
```
|
||||||
|
|
||||||
|
The `tls.mutual` parameter is expected to be a dictionary that deserializes into a
|
||||||
|
[`MutualTls`] structure:
|
||||||
|
|
||||||
|
| key | required | type |
|
||||||
|
|-------------|-----------|-------------------------------------------------------------|
|
||||||
|
| `ca_certs` | **_yes_** | Path or bytes to DER-encoded X.509 TLS cert chain. |
|
||||||
|
| `mandatory` | no | Boolean controlling whether the client _must_ authenticate. |
|
||||||
|
|
||||||
|
[`MutualTls`]: @api/rocket/config/struct.MutualTls.html
|
||||||
|
[`mtls`]: @api/rocket/mtls/index.html
|
||||||
|
|
||||||
|
Rocket reports if TLS and/or mTLS are enabled at launch time:
|
||||||
|
|
||||||
|
```text
|
||||||
|
🔧 Configured for debug.
|
||||||
|
...
|
||||||
|
>> tls: enabled w/mtls
|
||||||
|
```
|
||||||
|
|
||||||
|
The [TLS example](@example/tls) illustrates a fully configured TLS server with
|
||||||
|
mutual TLS.
|
||||||
|
|
||||||
|
! warning: Rocket's built-in TLS supports only TLS 1.2 and 1.3. This may not be
|
||||||
|
suitable for production use.
|
||||||
|
|
||||||
### 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
|
||||||
execution; there is no limit to the number of concurrent tasks. Due to a
|
execution; there is no limit to the number of concurrent tasks. Due to a
|
||||||
limitation in upstream async executers, unlike other values, the `workers`
|
limitation in upstream async executers, unlike other values, the `workers`
|
||||||
configuration value cannot be reconfigured or be configured from sources other
|
configuration value cannot be reconfigured or be configured from sources other
|
||||||
than those provided by [`Config::figment()`], detailed below. In other words,
|
than those provided by [`Config::figment()`]. In other words, only the values
|
||||||
only the values set by the `ROCKET_WORKERS` environment variable or in the
|
set by the `ROCKET_WORKERS` environment variable or in the `workers` property of
|
||||||
`workers` property of `Rocket.toml` will be considered - all other `workers`
|
`Rocket.toml` will be considered - all other `workers` values are ignored.
|
||||||
values are ignored.
|
|
||||||
|
|
||||||
## Extracting Values
|
## Extracting Values
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue