1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
//! `tide-acme` helps you serve HTTPS with Tide using automatic certificates, via Let's Encrypt and
//! ACME tls-alpn-01 challenges.
//!
//! To use `tide-acme`, set up HTTPS with Tide normally using `tide_rustls`, but instead of
//! specifying a certificate and key, call the `acme` method to configure automatic certificates in
//! the TLS listener:
//!
//! ```no_run
//! use tide_acme::{AcmeConfig, TideRustlsExt};
//! use tide_acme::rustls_acme::caches::DirCache;
//!
//! # async_std::task::block_on(async {
//! let mut app = tide::new();
//! app.at("/").get(|_| async { Ok("Hello TLS") });
//! app.listen(
//! tide_rustls::TlsListener::build().addrs("0.0.0.0:443").acme(
//! AcmeConfig::new(vec!["domain.example"])
//! .contact_push("mailto:admin@example.org")
//! .cache(DirCache::new("/srv/example/tide-acme-cache-dir")),
//! ),
//! )
//! .await?;
//! # tide::Result::Ok(())
//! # });
//! ```
//!
//! This will configure the TLS stack to obtain a certificate for the domain `domain.example`,
//! which must be a domain for which your Tide server handles HTTPS traffic.
//!
//! On initial startup, your server will register a certificate via Let's Encrypt. Let's Encrypt
//! will verify your server's control of the domain via an [ACME tls-alpn-01
//! challenge](https://tools.ietf.org/html/rfc8737), which the TLS listener configured by
//! `tide-acme` will respond to.
//!
//! You must supply a cache via [`AcmeConfig::cache`] or one of the other cache methods. This cache
//! will keep the ACME account key and registered certificates between runs, needed to avoid
//! hitting rate limits. You can use [`rustls_acme::caches::DirCache`] for a simple filesystem
//! cache, or implement your own caching using the `rustls_acme` cache traits.
//!
//! By default, `tide-acme` will use the Let's Encrypt staging environment, which is suitable for
//! testing purposes; it produces certificates signed by a staging root so that you can verify your
//! stack is working, but those certificates will not be trusted in browsers or other HTTPS
//! clients. The staging environment has more generous rate limits for use while testing.
//!
//! When you're ready to deploy to production, you can call `.directory_lets_encrypt(true)` to
//! switch to the production Let's Encrypt environment, which produces certificates trusted in
//! browsers and other HTTPS clients. The production environment has [stricter rate
//! limits](https://letsencrypt.org/docs/rate-limits/).
//!
//! `tide-acme` builds upon [`tide-rustls`](https://crates.io/crates/tide-rustls) and
//! [`rustls-acme`](https://crates.io/crates/rustls-acme).
#![forbid(unsafe_code)]
#![deny(missing_docs)]
use std::fmt::Debug;
use async_std::{net::TcpStream, stream::StreamExt};
use futures_lite::io::AsyncWriteExt;
pub use rustls_acme::{self, AcmeConfig};
use tide_rustls::async_rustls::{server::TlsStream, TlsAcceptor};
use tide_rustls::rustls::Session;
use tracing::{error, info, info_span, Instrument};
/// Custom TLS acceptor that answers ACME tls-alpn-01 challenges.
pub struct AcmeTlsAcceptor(TlsAcceptor);
impl AcmeTlsAcceptor {
/// Create a new TLS acceptor that answers ACME tls-alpn-01 challenges, based on the specified
/// configuration.
///
/// This will start a background task to manage certificates via ACME.
pub fn new<EC: 'static + Debug, EA: 'static + Debug>(config: AcmeConfig<EC, EA>) -> Self {
let mut state = config.state();
let acceptor = state.acceptor();
async_std::task::spawn(async move {
loop {
async {
match state
.next()
.await
.expect("AcmeState::next() always returns Some")
{
Ok(event) => info!(?event, "AcmeState::next() processed an event"),
Err(event) => error!(?event, "AcmeState::next() returned an error"),
}
}
.instrument(info_span!("AcmeState::next()"))
.await
}
});
Self(acceptor)
}
}
#[async_trait::async_trait]
impl tide_rustls::CustomTlsAcceptor for AcmeTlsAcceptor {
async fn accept(&self, stream: TcpStream) -> std::io::Result<Option<TlsStream<TcpStream>>> {
let mut tls = self.0.accept(stream).await?;
match tls.get_ref().1.get_alpn_protocol() {
Some(rustls_acme::acme::ACME_TLS_ALPN_NAME) => {
info_span!("AcmeTlsAcceptor::accept()")
.in_scope(|| info!("received acme-tls/1 validation request"));
tls.close().await?;
Ok(None)
}
_ => Ok(Some(tls)),
}
}
}
/// Extension trait for [`tide_rustls::TlsListenerBuilder`]
///
/// With this trait imported, `TlsListenerBuilder` will have an `acme` method to set up a custom
/// TLS acceptor that answers ACME tls-alpn-01 challenges.
pub trait TideRustlsExt {
/// Set up a custom TLS acceptor that answers ACME tls-alpn-01 challenges, using the specified
/// configuration.
///
/// This creates an [`AcmeTlsAcceptor`], which will start a background task to manage
/// certificates via ACME.
fn acme<EC: 'static + Debug, EA: 'static + Debug>(self, config: AcmeConfig<EC, EA>) -> Self;
}
impl<State> TideRustlsExt for tide_rustls::TlsListenerBuilder<State> {
fn acme<EC: 'static + Debug, EA: 'static + Debug>(self, config: AcmeConfig<EC, EA>) -> Self {
self.tls_acceptor(std::sync::Arc::new(AcmeTlsAcceptor::new(config)))
}
}