drawbridge_server/
builder.rs

1// SPDX-FileCopyrightText: 2022 Profian Inc. <opensource@profian.com>
2// SPDX-License-Identifier: Apache-2.0
3
4use super::{handle, App, Store, TlsConfig};
5use std::ops::Deref;
6
7use anyhow::{anyhow, Context};
8use async_std::fs::File;
9use async_std::path::Path;
10use async_std::sync::Arc;
11use axum::handler::Handler;
12use axum::routing::any;
13use axum::{Extension, Router};
14use cap_async_std::fs_utf8::Dir;
15use futures::lock::Mutex;
16use futures::TryFutureExt;
17use futures_rustls::TlsAcceptor;
18use openidconnect::url::Url;
19use tower_http::{
20    trace::{
21        DefaultOnBodyChunk, DefaultOnEos, DefaultOnFailure, DefaultOnRequest, DefaultOnResponse,
22        TraceLayer,
23    },
24    LatencyUnit,
25};
26use tracing::Level;
27
28/// OpenID Connect client configuration.
29#[derive(Debug)]
30pub struct OidcConfig {
31    pub audience: String,
32    pub issuer: Url,
33}
34
35#[derive(Debug, Clone, Default)]
36struct SpanMaker;
37
38impl<B> tower_http::trace::MakeSpan<B> for SpanMaker {
39    fn make_span(&mut self, request: &axum::http::request::Request<B>) -> tracing::span::Span {
40        let reqid = uuid::Uuid::new_v4();
41        tracing::span!(
42            Level::INFO,
43            "request",
44            method = %request.method(),
45            uri = %request.uri(),
46            version = ?request.version(),
47            headers = ?request.headers(),
48            request_id = %reqid,
49        )
50    }
51}
52
53/// [App] builder.
54pub struct Builder<S> {
55    store: S,
56    tls: TlsConfig,
57    oidc: OidcConfig,
58}
59
60impl<S: std::fmt::Debug> std::fmt::Debug for Builder<S> {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        f.debug_struct("Builder")
63            .field("store", &self.store)
64            .field("oidc", &self.oidc)
65            .finish()
66    }
67}
68
69impl<S: AsRef<Path>> Builder<S> {
70    /// Constructs a new [Builder].
71    pub fn new(store: S, tls: TlsConfig, oidc: OidcConfig) -> Self {
72        Self { store, tls, oidc }
73    }
74
75    /// Builds the application and returns Drawbridge instance as a [tower::MakeService].
76    pub async fn build(self) -> anyhow::Result<App> {
77        let Self { store, tls, oidc } = self;
78        let store_path = store.as_ref();
79        let store = File::open(store_path)
80            .and_then(|f| Store::new(Dir::from_std_file(f)))
81            .await
82            .context(anyhow!(
83                "failed to open store at `{}`",
84                store_path.to_string_lossy()
85            ))?;
86
87        let oidc_verifier =
88            crate::auth::OidcVerifier::new(oidc).context("failed to create OIDC verifier")?;
89
90        Ok(App {
91            make_service: Mutex::new(
92                Router::new()
93                    .fallback(handle.into_service())
94                    .route("/health", any(|| async {}))
95                    .layer(Extension(Arc::new(store)))
96                    .layer(Extension(Arc::new(oidc_verifier)))
97                    .layer(
98                        TraceLayer::new_for_http()
99                            .make_span_with(SpanMaker)
100                            .on_request(DefaultOnRequest::new().level(Level::INFO))
101                            .on_response(
102                                DefaultOnResponse::new()
103                                    .level(Level::INFO)
104                                    .latency_unit(LatencyUnit::Micros),
105                            )
106                            .on_body_chunk(DefaultOnBodyChunk::new())
107                            .on_eos(
108                                DefaultOnEos::new()
109                                    .level(Level::INFO)
110                                    .latency_unit(LatencyUnit::Micros),
111                            )
112                            .on_failure(
113                                DefaultOnFailure::new()
114                                    .level(Level::INFO)
115                                    .latency_unit(LatencyUnit::Micros),
116                            ),
117                    )
118                    .into_make_service(),
119            ),
120            tls: TlsAcceptor::from(Arc::new(tls.deref().clone())),
121        })
122    }
123}