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
|
||||
//! while necessary.
|
||||
|
||||
#[doc(hidden)] pub use hyper::{Body, Error, Request, Response, Version};
|
||||
#[doc(hidden)] pub use hyper::body::{Bytes, HttpBody, Sender as BodySender};
|
||||
#[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};
|
||||
#[doc(hidden)] pub use hyper::*;
|
||||
#[doc(hidden)] pub use http::*;
|
||||
|
||||
/// Reexported http header types.
|
||||
pub mod header {
|
||||
|
|
|
@ -89,6 +89,98 @@ pub enum Error {
|
|||
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)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
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 {
|
||||
&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 {
|
||||
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> {
|
||||
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> {
|
||||
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>> {
|
||||
&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> {
|
||||
let uint: bigint::BigUint = number.parse().ok()?;
|
||||
Some(&uint == self.serial())
|
||||
|
@ -173,22 +375,116 @@ impl<'a> Deref for Certificate<'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> {
|
||||
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> + '_ {
|
||||
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> {
|
||||
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> + '_ {
|
||||
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 {
|
||||
self.0.as_raw().is_empty()
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ all-features = true
|
|||
[features]
|
||||
default = []
|
||||
tls = ["rocket_http/tls"]
|
||||
mtls = ["rocket_http/mtls", "tls"]
|
||||
secrets = ["rocket_http/private-cookies"]
|
||||
json = ["serde_json", "tokio/io-util"]
|
||||
msgpack = ["rmp-serde", "tokio/io-util"]
|
||||
|
|
|
@ -70,13 +70,17 @@ pub struct Config {
|
|||
pub port: u16,
|
||||
/// Number of threads to use for executing futures. **(default: `num_cores`)**
|
||||
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.
|
||||
/// **(default: `"Rocket"`)**
|
||||
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`)**
|
||||
#[cfg(feature = "tls")]
|
||||
#[cfg_attr(nightly, doc(cfg(feature = "tls")))]
|
||||
|
@ -89,14 +93,10 @@ pub struct Config {
|
|||
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
||||
#[serde(serialize_with = "SecretKey::serialize_zero")]
|
||||
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()`])**
|
||||
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`)**
|
||||
#[serde(deserialize_with = "figment::util::bool_from_str_or_int")]
|
||||
pub cli_colors: bool,
|
||||
|
@ -167,16 +167,16 @@ impl Config {
|
|||
address: Ipv4Addr::new(127, 0, 0, 1).into(),
|
||||
port: 8000,
|
||||
workers: num_cpus::get(),
|
||||
keep_alive: 5,
|
||||
limits: Limits::default(),
|
||||
ident: Ident::default(),
|
||||
limits: Limits::default(),
|
||||
temp_dir: std::env::temp_dir().into(),
|
||||
keep_alive: 5,
|
||||
#[cfg(feature = "tls")]
|
||||
tls: None,
|
||||
#[cfg(feature = "secrets")]
|
||||
secret_key: SecretKey::zero(),
|
||||
temp_dir: std::env::temp_dir().into(),
|
||||
log_level: LogLevel::Normal,
|
||||
shutdown: Shutdown::default(),
|
||||
log_level: LogLevel::Normal,
|
||||
cli_colors: true,
|
||||
__non_exhaustive: (),
|
||||
}
|
||||
|
@ -316,31 +316,62 @@ impl Config {
|
|||
#[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) {
|
||||
use crate::log::PaintExt;
|
||||
|
||||
launch_info!("{}Configured for {}.", Paint::emoji("🔧 "), self.profile);
|
||||
|
||||
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());
|
||||
fn bold<T: std::fmt::Display>(val: T) -> Paint<T> {
|
||||
Paint::default(val).bold()
|
||||
}
|
||||
|
||||
launch_info_!("limits: {}", Paint::default(&self.limits).bold());
|
||||
match self.tls_enabled() {
|
||||
true => launch_info_!("tls: {}", Paint::default("enabled").bold()),
|
||||
false => launch_info_!("tls: {}", Paint::default("disabled").bold()),
|
||||
launch_info!("{}Configured for {}.", Paint::emoji("🔧 "), self.profile);
|
||||
launch_info_!("address: {}", bold(&self.address));
|
||||
launch_info_!("port: {}", bold(&self.port));
|
||||
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")] {
|
||||
launch_info_!("secret key: {:?}", Paint::default(&self.secret_key).bold());
|
||||
launch_info_!("secret key: {}", bold(&self.secret_key));
|
||||
if !self.secret_key.is_provided() {
|
||||
warn!("secrets enabled without a stable `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_!("log level: {}", Paint::default(self.log_level).bold());
|
||||
launch_info_!("cli colors: {}", Paint::default(&self.cli_colors).bold());
|
||||
launch_info_!("shutdown: {}", Paint::default(&self.shutdown).bold());
|
||||
launch_info_!("shutdown: {}", bold(&self.shutdown));
|
||||
launch_info_!("log level: {}", bold(self.log_level));
|
||||
launch_info_!("cli colors: {}", bold(&self.cli_colors));
|
||||
|
||||
// Check for now depreacted config values.
|
||||
for (key, replacement) in Self::DEPRECATED_KEYS {
|
||||
|
@ -399,7 +429,6 @@ impl Config {
|
|||
/// The default profile: "debug" on `debug`, "release" on `release`.
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub const DEFAULT_PROFILE: Profile = Self::RELEASE_PROFILE;
|
||||
|
||||
}
|
||||
|
||||
/// Associated constants for stringy versions of configuration parameters.
|
||||
|
|
|
@ -131,6 +131,9 @@ pub use ident::Ident;
|
|||
#[cfg(feature = "tls")]
|
||||
pub use tls::{TlsConfig, CipherSuite};
|
||||
|
||||
#[cfg(feature = "mtls")]
|
||||
pub use tls::MutualTls;
|
||||
|
||||
#[cfg(feature = "secrets")]
|
||||
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]
|
||||
fn test_profiles_merge() {
|
||||
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 {
|
||||
if self.is_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
|
||||
/// 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
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::config::{Config, TlsConfig, CipherSuite};
|
||||
///
|
||||
/// // From a manually constructed figment.
|
||||
/// let figment = rocket::Config::figment()
|
||||
/// #[launch]
|
||||
/// 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.key", vec![0; 32]));
|
||||
///
|
||||
/// let config = rocket::Config::from(figment);
|
||||
/// let tls_config = config.tls.as_ref().unwrap();
|
||||
/// assert!(tls_config.certs().is_left());
|
||||
/// assert!(tls_config.key().is_right());
|
||||
/// assert_eq!(tls_config.ciphers().count(), 9);
|
||||
/// 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());
|
||||
/// #
|
||||
/// # let config = rocket::Config::from(figment);
|
||||
/// # let tls_config = config.tls.as_ref().unwrap();
|
||||
/// # assert!(tls_config.certs().is_left());
|
||||
/// # assert!(tls_config.key().is_right());
|
||||
/// # assert_eq!(tls_config.ciphers().count(), 9);
|
||||
/// # assert!(!tls_config.prefer_server_cipher_order());
|
||||
/// ```
|
||||
#[derive(PartialEq, Debug, Clone, Deserialize, Serialize)]
|
||||
#[cfg_attr(nightly, doc(cfg(feature = "tls")))]
|
||||
#[serde(deny_unknown_fields)]
|
||||
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>>,
|
||||
/// Path or raw bytes to DER-encoded ASN.1 key in either PKCS#8 or PKCS#1
|
||||
/// format.
|
||||
/// Path to a PEM file with, or raw bytes for, DER-encoded private key in
|
||||
/// either PKCS#8 or PKCS#1 format.
|
||||
pub(crate) key: Either<RelativePathBuf, Vec<u8>>,
|
||||
/// List of TLS cipher suites in server-preferred order.
|
||||
#[serde(default = "CipherSuite::default_set")]
|
||||
|
@ -78,11 +97,77 @@ pub struct TlsConfig {
|
|||
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)]
|
||||
#[cfg(feature = "mtls")]
|
||||
#[cfg_attr(nightly, doc(cfg(feature = "mtls")))]
|
||||
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>>,
|
||||
/// 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(deserialize_with = "figment::util::bool_from_str_or_int")]
|
||||
pub mandatory: bool,
|
||||
|
|
|
@ -52,7 +52,7 @@ pub struct StreamReader<'r> {
|
|||
/// The current state of `StreamReader` `AsyncRead` adapter.
|
||||
enum State {
|
||||
Pending,
|
||||
Partial(Cursor<hyper::Bytes>),
|
||||
Partial(Cursor<hyper::body::Bytes>),
|
||||
Done,
|
||||
}
|
||||
|
||||
|
@ -257,7 +257,7 @@ impl AsyncRead for DataStream<'_> {
|
|||
}
|
||||
|
||||
impl Stream for StreamKind<'_> {
|
||||
type Item = io::Result<hyper::Bytes>;
|
||||
type Item = io::Result<hyper::body::Bytes>;
|
||||
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
|
|
|
@ -63,6 +63,7 @@
|
|||
//! |-----------|---------------------------------------------------------|
|
||||
//! | `secrets` | Support for authenticated, encrypted [private cookies]. |
|
||||
//! | `tls` | Support for [TLS] encrypted connections. |
|
||||
//! | `mtls` | Support for verified clients via [mutual TLS]. |
|
||||
//! | `json` | Support for [JSON (de)serialization]. |
|
||||
//! | `msgpack` | Support for [MessagePack (de)serialization]. |
|
||||
//! | `uuid` | Support for [UUID value parsing and (de)serialization]. |
|
||||
|
@ -79,6 +80,7 @@
|
|||
//! [UUID value parsing and (de)serialization]: crate::serde::uuid
|
||||
//! [private cookies]: https://rocket.rs/v0.5-rc/guide/requests/#private-cookies
|
||||
//! [TLS]: https://rocket.rs/v0.5-rc/guide/configuration/#tls
|
||||
//! [mutual TLS]: crate::mtls
|
||||
//!
|
||||
//! ## Configuration
|
||||
//!
|
||||
|
@ -137,6 +139,10 @@ pub mod http {
|
|||
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.
|
||||
mod trip_wire;
|
||||
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)]
|
||||
pub use crate::response::flash::FlashMessage;
|
||||
|
||||
pub(crate) use self::request::ConnectionMeta;
|
||||
|
||||
crate::export! {
|
||||
/// Store and immediately retrieve a value `$v` in `$request`'s local cache
|
||||
/// using a locally generated anonymous type to avoid type conflicts.
|
||||
|
|
|
@ -8,16 +8,16 @@ use state::{Container, Storage};
|
|||
use futures::future::BoxFuture;
|
||||
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::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::{ContentType, Accept, MediaType, CookieJar, Cookie};
|
||||
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.
|
||||
///
|
||||
|
@ -28,12 +28,19 @@ use crate::data::Limits;
|
|||
pub struct Request<'r> {
|
||||
method: Atomic<Method>,
|
||||
uri: Origin<'r>,
|
||||
host: Option<Host<'r>>,
|
||||
headers: HeaderMap<'r>,
|
||||
remote: Option<SocketAddr>,
|
||||
pub(crate) connection: ConnectionMeta,
|
||||
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 rocket: &'r Rocket<Orbit>,
|
||||
pub route: Atomic<Option<&'r Route>>,
|
||||
|
@ -41,6 +48,7 @@ pub(crate) struct RequestState<'r> {
|
|||
pub accept: Storage<Option<Accept>>,
|
||||
pub content_type: Storage<Option<ContentType>>,
|
||||
pub cache: Arc<Container![Send + Sync]>,
|
||||
pub host: Option<Host<'r>>,
|
||||
}
|
||||
|
||||
impl Request<'_> {
|
||||
|
@ -48,9 +56,8 @@ impl Request<'_> {
|
|||
Request {
|
||||
method: Atomic::new(self.method()),
|
||||
uri: self.uri.clone(),
|
||||
host: self.host.clone(),
|
||||
headers: self.headers.clone(),
|
||||
remote: self.remote,
|
||||
connection: self.connection.clone(),
|
||||
state: self.state.clone(),
|
||||
}
|
||||
}
|
||||
|
@ -65,6 +72,7 @@ impl RequestState<'_> {
|
|||
accept: self.accept.clone(),
|
||||
content_type: self.content_type.clone(),
|
||||
cache: self.cache.clone(),
|
||||
host: self.host.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -79,10 +87,12 @@ impl<'r> Request<'r> {
|
|||
) -> Request<'r> {
|
||||
Request {
|
||||
uri,
|
||||
host: None,
|
||||
method: Atomic::new(method),
|
||||
headers: HeaderMap::new(),
|
||||
remote: None,
|
||||
connection: ConnectionMeta {
|
||||
remote: None,
|
||||
client_certificates: None,
|
||||
},
|
||||
state: RequestState {
|
||||
rocket,
|
||||
route: Atomic::new(None),
|
||||
|
@ -90,6 +100,7 @@ impl<'r> Request<'r> {
|
|||
accept: Storage::new(),
|
||||
content_type: Storage::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
|
||||
/// `: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 ⚠️
|
||||
///
|
||||
/// Using the user-controlled `host` to construct URLs is a security hazard!
|
||||
|
@ -257,7 +272,7 @@ impl<'r> Request<'r> {
|
|||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn host(&self) -> Option<&Host<'r>> {
|
||||
self.host.as_ref()
|
||||
self.state.host.as_ref()
|
||||
}
|
||||
|
||||
/// Sets the host of `self` to `host`.
|
||||
|
@ -282,7 +297,7 @@ impl<'r> Request<'r> {
|
|||
/// ```
|
||||
#[inline(always)]
|
||||
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
|
||||
|
@ -314,7 +329,7 @@ impl<'r> Request<'r> {
|
|||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn remote(&self) -> Option<SocketAddr> {
|
||||
self.remote
|
||||
self.connection.remote
|
||||
}
|
||||
|
||||
/// Sets the remote address of `self` to `address`.
|
||||
|
@ -337,7 +352,7 @@ impl<'r> Request<'r> {
|
|||
/// ```
|
||||
#[inline(always)]
|
||||
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
|
||||
|
@ -949,8 +964,8 @@ impl<'r> Request<'r> {
|
|||
/// Convert from Hyper types into a Rocket Request.
|
||||
pub(crate) fn from_hyp(
|
||||
rocket: &'r Rocket<Orbit>,
|
||||
hyper: &'r hyper::RequestParts,
|
||||
addr: SocketAddr
|
||||
hyper: &'r hyper::request::Parts,
|
||||
connection: Option<ConnectionMeta>,
|
||||
) -> Result<Request<'r>, Error<'r>> {
|
||||
// Ensure that the method is known. TODO: Allow made-up methods?
|
||||
let method = Method::from_hyp(&hyper.method)
|
||||
|
@ -965,11 +980,13 @@ impl<'r> Request<'r> {
|
|||
|
||||
// Construct the request object.
|
||||
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,
|
||||
// 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())
|
||||
} else {
|
||||
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 crate::Request;
|
||||
|
@ -17,9 +16,8 @@ macro_rules! assert_headers {
|
|||
|
||||
// Create a valid `Rocket` and convert the hyper req to a Rocket one.
|
||||
let client = Client::debug_with(vec![]).unwrap();
|
||||
let addr = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 8000).into();
|
||||
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.
|
||||
let actual_headers = req.headers();
|
||||
|
|
|
@ -12,6 +12,7 @@ use crate::form::Form;
|
|||
use crate::outcome::Outcome;
|
||||
use crate::error::{Error, ErrorKind};
|
||||
use crate::ext::{AsyncReadExt, CancellableListener, CancellableIo};
|
||||
use crate::request::ConnectionMeta;
|
||||
|
||||
use crate::http::{uri::Origin, hyper, Method, Status, Header};
|
||||
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.
|
||||
async fn hyper_service_fn(
|
||||
rocket: Arc<Rocket<Orbit>>,
|
||||
addr: std::net::SocketAddr,
|
||||
conn: ConnectionMeta,
|
||||
hyp_req: hyper::Request<hyper::Body>,
|
||||
) -> Result<hyper::Response<hyper::Body>, io::Error> {
|
||||
// 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 {
|
||||
// Convert a Hyper request into a Rocket request.
|
||||
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) => {
|
||||
// Convert into Rocket `Data`, dispatch request, write response.
|
||||
let mut data = Data::from(&mut h_body);
|
||||
|
@ -366,20 +367,18 @@ impl Rocket<Orbit> {
|
|||
.map_err(|e| Error::new(ErrorKind::Io(e)))?;
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
if let Some(ref config) = self.config.tls {
|
||||
use crate::http::tls::TlsListener;
|
||||
if self.config.tls_enabled() {
|
||||
if let Some(ref config) = self.config.tls {
|
||||
use crate::http::tls::TlsListener;
|
||||
|
||||
let (certs, key) = config.to_readers().map_err(ErrorKind::Io)?;
|
||||
let ciphers = config.rustls_ciphers();
|
||||
let server_order = config.prefer_server_cipher_order;
|
||||
let l = TlsListener::bind(addr, certs, key, ciphers, server_order).await
|
||||
.map_err(ErrorKind::Bind)?;
|
||||
|
||||
addr = l.local_addr().unwrap_or(addr);
|
||||
self.config.address = addr.ip();
|
||||
self.config.port = addr.port();
|
||||
ready(&mut self).await;
|
||||
return self.http_server(l).await;
|
||||
let conf = config.to_native_config().map_err(ErrorKind::Io)?;
|
||||
let l = TlsListener::bind(addr, conf).await.map_err(ErrorKind::Bind)?;
|
||||
addr = l.local_addr().unwrap_or(addr);
|
||||
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)?;
|
||||
|
@ -443,10 +442,14 @@ impl Rocket<Orbit> {
|
|||
let rocket = Arc::new(self);
|
||||
let service_fn = move |conn: &CancellableIo<_, L::Connection>| {
|
||||
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 {
|
||||
Ok::<_, std::convert::Infallible>(hyper::service_fn(move |req| {
|
||||
hyper_service_fn(rocket.clone(), remote, req)
|
||||
Ok::<_, std::convert::Infallible>(hyper::service::service_fn(move |req| {
|
||||
hyper_service_fn(rocket.clone(), connection.clone(), req)
|
||||
}))
|
||||
}
|
||||
};
|
||||
|
@ -457,7 +460,7 @@ impl Rocket<Orbit> {
|
|||
.http1_keepalive(http1_keepalive)
|
||||
.http1_preserve_header_case(true)
|
||||
.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())
|
||||
.map_err(|e| Error::new(ErrorKind::Runtime(Box::new(e))));
|
||||
|
||||
|
|
|
@ -102,6 +102,7 @@ function test_core() {
|
|||
FEATURES=(
|
||||
secrets
|
||||
tls
|
||||
mtls
|
||||
json
|
||||
msgpack
|
||||
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.
|
||||
```
|
||||
|
||||
Next time the server is run, Rocket will report that TLS is enabled during
|
||||
ignition:
|
||||
|
||||
```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:
|
||||
The `tls` parameter is expected to be a dictionary that deserializes into a
|
||||
[`TlsConfig`] structure:
|
||||
|
||||
| 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. |
|
||||
| `ciphers` | no | Array of [`CipherSuite`]s to enable. |
|
||||
| `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
|
||||
[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
|
||||
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
|
||||
|
||||
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
|
||||
limitation in upstream async executers, unlike other values, the `workers`
|
||||
configuration value cannot be reconfigured or be configured from sources other
|
||||
than those provided by [`Config::figment()`], detailed below. In other words,
|
||||
only the values set by the `ROCKET_WORKERS` environment variable or in the
|
||||
`workers` property of `Rocket.toml` will be considered - all other `workers`
|
||||
values are ignored.
|
||||
than those provided by [`Config::figment()`]. In other words, only the values
|
||||
set by the `ROCKET_WORKERS` environment variable or in the `workers` property of
|
||||
`Rocket.toml` will be considered - all other `workers` values are ignored.
|
||||
|
||||
## Extracting Values
|
||||
|
||||
|
|
Loading…
Reference in New Issue