Skip to main content

postrust_proxy/saas/
options.rs

1//! Settings for the SaaS control plane.
2//!
3//! Gathered into one place so the defaults are visible together and an
4//! embedder changes them by naming them, rather than by finding which
5//! constructor took which positional argument.
6
7use crate::saas::validation::ReservedDomains;
8use std::sync::Arc;
9use std::time::Duration;
10
11/// The audience a proxy JWT must name when none is configured.
12///
13/// Required, rather than optional, because the main Postrust server may be
14/// configured with the same signing secret: without an audience, a token
15/// minted for it would be accepted here too.
16pub const DEFAULT_JWT_AUDIENCE: &str = "postrust-proxy";
17
18/// Everything [`SaasState`](crate::saas::handlers::SaasState) is configured
19/// with.
20///
21/// `#[non_exhaustive]`, so build it from [`Default`] and set fields:
22///
23/// ```
24/// # use postrust_proxy::saas::SaasOptions;
25/// let mut options = SaasOptions::default();
26/// options.jwt_secret = Some("secret".into());
27/// ```
28#[derive(Clone, Debug)]
29#[non_exhaustive]
30pub struct SaasOptions {
31    /// HMAC secret for proxy JWTs. `None` refuses every JWT.
32    pub jwt_secret: Option<String>,
33    /// The `aud` a JWT must carry. Defaults to [`DEFAULT_JWT_AUDIENCE`].
34    pub jwt_audience: String,
35    /// The `iss` a JWT must carry, if set. Unchecked by default.
36    pub jwt_issuer: Option<String>,
37    /// Rules for domains: what may be registered and how verification runs.
38    pub domains: DomainPolicy,
39}
40
41impl Default for SaasOptions {
42    fn default() -> Self {
43        Self {
44            jwt_secret: None,
45            jwt_audience: DEFAULT_JWT_AUDIENCE.to_string(),
46            jwt_issuer: None,
47            domains: DomainPolicy::default(),
48        }
49    }
50}
51
52/// Rules for registering, verifying and certifying domains.
53#[derive(Clone, Debug)]
54#[non_exhaustive]
55pub struct DomainPolicy {
56    /// Names no tenant may register. Put the platform's own hostnames here.
57    pub reserved: ReservedDomains,
58    /// How long an unverified registration holds its name.
59    ///
60    /// After this a pending (or failed) row stops blocking the name: the next
61    /// registration of it by anyone removes the stale row, and the stale row
62    /// can no longer be verified. Otherwise a tenant could register a name it
63    /// does not control and hold it against its real owner indefinitely.
64    /// The verification challenge expires at the same moment. Seven days.
65    pub pending_ttl: Duration,
66    /// The shortest gap allowed between two verification attempts for one
67    /// domain. Each attempt makes outbound DNS or HTTP requests. Ten seconds.
68    pub verification_min_interval: Duration,
69    /// How many verification attempts one registration gets. After that, the
70    /// tenant has to delete and re-add the domain. Fifty.
71    pub verification_max_attempts: i32,
72    /// What an uploaded certificate chain must chain to.
73    pub upload_trust: UploadTrust,
74}
75
76impl Default for DomainPolicy {
77    fn default() -> Self {
78        Self {
79            reserved: ReservedDomains::default(),
80            pending_ttl: Duration::from_secs(7 * 24 * 60 * 60),
81            verification_min_interval: Duration::from_secs(10),
82            verification_max_attempts: 50,
83            upload_trust: UploadTrust::PublicRoots,
84        }
85    }
86}
87
88/// What an uploaded certificate chain has to be trusted by.
89#[derive(Clone, Debug)]
90pub enum UploadTrust {
91    /// The Mozilla root programme, as bundled by `webpki-roots`. The default:
92    /// the certificate is served to browsers, which will trust nothing else.
93    PublicRoots,
94    /// These roots, for a platform whose tenants use an internal CA.
95    Roots(Arc<rustls::RootCertStore>),
96    /// No chain check. The key must still match, the leaf must still be in
97    /// date and cover the domain, and the domain must still be verified.
98    Unchecked,
99}