scufflecloud_email/
services.rs

1use std::net::SocketAddr;
2use std::sync::Arc;
3
4use anyhow::Context;
5use axum::Extension;
6use axum::http::StatusCode;
7use rustls::pki_types::pem::PemObject;
8use rustls::pki_types::{CertificateDer, PrivateKeyDer};
9use tower_http::trace::TraceLayer;
10
11mod email;
12
13#[derive(Debug)]
14pub struct EmailSvc<G> {
15    _phantom: std::marker::PhantomData<G>,
16}
17
18impl<G> Default for EmailSvc<G> {
19    fn default() -> Self {
20        Self {
21            _phantom: std::marker::PhantomData,
22        }
23    }
24}
25
26impl<G: email_traits::Global> scuffle_bootstrap::Service<G> for EmailSvc<G> {
27    async fn run(self, global: Arc<G>, ctx: scuffle_context::Context) -> anyhow::Result<()> {
28        // gRPC
29        let email_svc = pb::scufflecloud::email::v1::email_service_server::EmailServiceServer::new(EmailSvc::<G>::default());
30
31        let reflection_v1_svc = tonic_reflection::server::Builder::configure()
32            .register_encoded_file_descriptor_set(pb::ANNOTATIONS_PB)
33            .build_v1()?;
34        let reflection_v1alpha_svc = tonic_reflection::server::Builder::configure()
35            .register_encoded_file_descriptor_set(pb::ANNOTATIONS_PB)
36            .build_v1alpha()?;
37
38        let mut builder = tonic::service::Routes::builder();
39        builder.add_service(email_svc);
40        builder.add_service(reflection_v1_svc);
41        builder.add_service(reflection_v1alpha_svc);
42        let grpc_router = builder.routes().prepare().into_axum_router();
43
44        let router = axum::Router::new()
45            .merge(grpc_router)
46            .layer(TraceLayer::new_for_http())
47            .layer(Extension(Arc::clone(&global)))
48            .fallback(StatusCode::NOT_FOUND);
49
50        // Internal authentication via mTLS
51        let root_cert =
52            CertificateDer::from_pem_slice(global.mtls_root_cert_pem()).context("failed to parse mTLS root cert")?;
53        let cert = CertificateDer::from_pem_slice(global.mtls_cert_pem()).context("failed to parse mTLS cert")?;
54        let private_key =
55            PrivateKeyDer::from_pem_slice(global.mtls_private_key_pem()).context("failed to parse mTLS private key")?;
56
57        let mut root_cert_store = rustls::RootCertStore::empty();
58        root_cert_store
59            .add(root_cert.clone())
60            .context("failed to add mTLS root cert to root cert store")?;
61        let cert_chain = vec![cert, root_cert];
62
63        let rustls_client_verifier = rustls::server::WebPkiClientVerifier::builder(Arc::new(root_cert_store))
64            .build()
65            .context("failed to create client cert verifier")?;
66        let rustls_server_config = rustls::ServerConfig::builder()
67            .with_client_cert_verifier(rustls_client_verifier)
68            .with_single_cert(cert_chain, private_key)
69            .context("failed to create rustls ServerConfig")?;
70
71        scuffle_http::HttpServer::builder()
72            .tower_make_service_with_addr(router.into_make_service_with_connect_info::<SocketAddr>())
73            .bind(global.service_bind())
74            .ctx(ctx)
75            .rustls_config(rustls_server_config)
76            .build()
77            .run()
78            .await?;
79
80        Ok(())
81    }
82}