penumbra_sdk_auto_https/
lib.rs

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
//! Automatic HTTPS certificate management facilities.
//!
//! See [`axum_acceptor`] for more information.

use {
    anyhow::Error,
    futures::Future,
    rustls::ServerConfig,
    rustls_acme::{axum::AxumAcceptor, caches::DirCache, AcmeConfig, AcmeState},
    std::{fmt::Debug, path::PathBuf, sync::Arc},
};

/// Protocols supported by this server, in order of preference.
///
/// See [rfc7301] for more info on ALPN.
///
/// [rfc7301]: https://datatracker.ietf.org/doc/html/rfc7301
//
//  We also permit HTTP1.1 for backwards-compatibility, specifically for grpc-web.
const ALPN_PROTOCOLS: [&[u8]; 2] = [b"h2", b"http/1.1"];

/// The location of the file-based certificate cache.
//  NB: this must not be an absolute path see [Path::join].
const CACHE_DIR: &str = "tokio_rustls_acme_cache";

/// Use ACME to resolve certificates and handle new connections.
///
/// This returns a tuple containing an [`AxumAcceptor`] that may be used with [`axum_server`], and
/// a [`Future`] that represents the background task to poll and log for changes in the
/// certificate environment.
pub fn axum_acceptor(
    home: PathBuf,
    domain: String,
    production_api: bool,
) -> (AxumAcceptor, impl Future<Output = Result<(), Error>>) {
    // Use a file-based cache located within the home directory.
    let cache = home.join(CACHE_DIR);
    let cache = DirCache::new(cache);

    // Create an ACME client, which we will use to resolve certificates.
    let state = AcmeConfig::new(vec![domain])
        .cache(cache)
        .directory_lets_encrypt(production_api)
        .state();

    // Define our server configuration, using the ACME certificate resolver.
    let mut rustls_config = ServerConfig::builder()
        .with_no_client_auth()
        .with_cert_resolver(state.resolver());
    rustls_config.alpn_protocols = self::alpn_protocols();
    let rustls_config = Arc::new(rustls_config);

    // Return our connection acceptor and our background worker task.
    let acceptor = state.axum_acceptor(rustls_config.clone());
    let worker = self::acme_worker(state);
    (acceptor, worker)
}

/// This function defines the task responsible for handling ACME events.
///
/// This function will never return, unless an error is encountered.
#[tracing::instrument(level = "error", skip_all)]
async fn acme_worker<EC, EA>(mut state: AcmeState<EC, EA>) -> Result<(), anyhow::Error>
where
    EC: Debug + 'static,
    EA: Debug + 'static,
{
    use futures::StreamExt;
    loop {
        match state.next().await {
            Some(Ok(ok)) => tracing::debug!("received acme event: {:?}", ok),
            Some(Err(err)) => {
                tracing::error!("acme error: {:?}", err);
                anyhow::bail!("exiting due to acme error");
            }
            None => {
                debug_assert!(false, "acme worker unexpectedly reached end-of-stream");
                tracing::error!("acme worker unexpectedly reached end-of-stream");
                anyhow::bail!("unexpected end-of-stream");
            }
        }
    }
}

/// Returns a vector of the protocols supported by this server.
///
/// This is a convenience method to retrieve an owned copy of [`ALPN_PROTOCOLS`].
fn alpn_protocols() -> Vec<Vec<u8>> {
    ALPN_PROTOCOLS.into_iter().map(<[u8]>::to_vec).collect()
}