1use crate::error::{ProxyError, ProxyResult};
6use crate::saas::db;
7use crate::saas::options::{DomainPolicy, UploadTrust};
8use crate::saas::types::*;
9use crate::saas::validation;
10use crate::saas::verification::DomainVerificationService;
11use rand::Rng;
12use sqlx::PgPool;
13use std::sync::Arc;
14use uuid::Uuid;
15
16pub struct DomainManager {
18 pool: PgPool,
19 verification_service: Arc<DomainVerificationService>,
20 policy: DomainPolicy,
21}
22
23impl DomainManager {
24 pub fn new(pool: PgPool, verification_service: Arc<DomainVerificationService>) -> Self {
26 Self::with_policy(pool, verification_service, DomainPolicy::default())
27 }
28
29 pub fn with_policy(
31 pool: PgPool,
32 verification_service: Arc<DomainVerificationService>,
33 policy: DomainPolicy,
34 ) -> Self {
35 Self {
36 pool,
37 verification_service,
38 policy,
39 }
40 }
41
42 pub async fn create_domain(
52 &self,
53 tenant_id: Uuid,
54 mut req: CreateDomainRequest,
55 ) -> ProxyResult<DomainResponse> {
56 req.domain = validation::normalize_domain(&req.domain)?;
57 if self.policy.reserved.is_reserved(&req.domain) {
58 return Err(ProxyError::Validation(format!(
59 "{} is reserved and cannot be registered",
60 req.domain
61 )));
62 }
63
64 let verification_token = generate_verification_token();
65 let expected_value = format!("postrust-verify={}", verification_token);
66
67 let domain = db::register_domain(
68 &self.pool,
69 tenant_id,
70 &req,
71 &verification_token,
72 &expected_value,
73 self.policy.pending_ttl,
74 )
75 .await?;
76
77 let instructions = match domain.verification_method {
79 VerificationMethod::Dns => {
80 VerificationInstructions::dns(&domain.domain, &verification_token)
81 }
82 VerificationMethod::Http => {
83 VerificationInstructions::http(&domain.domain, &verification_token)
84 }
85 };
86
87 Ok(DomainResponse {
88 domain,
89 verification_instructions: instructions,
90 })
91 }
92
93 pub async fn get_domain(
95 &self,
96 id: Uuid,
97 tenant_id: Uuid,
98 ) -> ProxyResult<Option<DomainResponse>> {
99 let domain = db::get_domain_for_tenant(&self.pool, id, tenant_id).await?;
100
101 Ok(domain.map(|d| {
102 let instructions = match d.verification_method {
103 VerificationMethod::Dns => {
104 VerificationInstructions::dns(&d.domain, &d.verification_token)
105 }
106 VerificationMethod::Http => {
107 VerificationInstructions::http(&d.domain, &d.verification_token)
108 }
109 };
110 DomainResponse {
111 domain: d,
112 verification_instructions: instructions,
113 }
114 }))
115 }
116
117 pub async fn list_domains(&self, tenant_id: Uuid) -> ProxyResult<Vec<Domain>> {
119 db::list_domains(&self.pool, tenant_id).await
120 }
121
122 pub async fn delete_domain(
130 &self,
131 id: Uuid,
132 tenant_id: Uuid,
133 cert_store: &crate::tls::CertificateStore,
134 ) -> ProxyResult<bool> {
135 let Some(certificates) = db::delete_domain(&self.pool, id, tenant_id).await? else {
136 return Ok(false);
137 };
138 for name in certificates {
139 if let Err(error) = cert_store.remove(&name).await {
140 tracing::warn!(domain = %name, %error, "could not clear a cached certificate");
144 }
145 }
146 Ok(true)
147 }
148
149 pub async fn verify_domain(
158 &self,
159 id: Uuid,
160 tenant_id: Uuid,
161 ) -> ProxyResult<VerificationResult> {
162 let domain = match db::start_verification_attempt(
163 &self.pool,
164 id,
165 tenant_id,
166 self.policy.verification_min_interval,
167 self.policy.verification_max_attempts,
168 self.policy.pending_ttl,
169 )
170 .await?
171 {
172 db::AttemptStart::Started(domain) => domain,
173 db::AttemptStart::AlreadyVerified => return Ok(VerificationResult::Verified),
174 db::AttemptStart::NotFound => {
175 return Err(ProxyError::NotFound("Domain not found".into()))
176 }
177 db::AttemptStart::TooSoon => return Err(ProxyError::RateLimitExceeded),
178 db::AttemptStart::Exhausted => {
179 return Ok(VerificationResult::Failed {
180 reason: "This domain has used all its verification attempts; \
181 delete it and add it again"
182 .into(),
183 })
184 }
185 db::AttemptStart::Expired => {
186 return Ok(VerificationResult::Failed {
187 reason: "The verification challenge has expired; \
188 delete the domain and add it again"
189 .into(),
190 })
191 }
192 };
193
194 let result = match domain.verification_method {
196 VerificationMethod::Dns => {
197 self.verification_service
198 .verify_dns(&domain.domain, &domain.verification_token)
199 .await
200 }
201 VerificationMethod::Http => {
202 self.verification_service
203 .verify_http(&domain.domain, &domain.verification_token)
204 .await
205 }
206 };
207
208 match &result {
209 VerificationResult::Verified => {
210 db::mark_verified(&self.pool, id).await?;
220 tracing::info!(domain = %domain.domain, "Domain verified successfully");
221 }
222 VerificationResult::Failed { reason } => {
223 tracing::warn!(domain = %domain.domain, reason = %reason, "Domain verification failed");
224 }
225 VerificationResult::Pending => {}
226 }
227
228 Ok(result)
229 }
230
231 pub async fn find_live_http_challenge(
237 &self,
238 token: &str,
239 ) -> ProxyResult<Option<db::HttpChallenge>> {
240 db::find_live_http_challenge(&self.pool, token).await
241 }
242
243 pub async fn update_domain(
248 &self,
249 id: Uuid,
250 tenant_id: Uuid,
251 req: UpdateDomainRequest,
252 ) -> ProxyResult<Domain> {
253 db::update_domain(
254 &self.pool,
255 id,
256 tenant_id,
257 req.verification_method.as_ref(),
258 req.ssl_provider.as_ref(),
259 )
260 .await?
261 .ok_or_else(|| ProxyError::NotFound("Domain not found".into()))
262 }
263
264 pub async fn upload_certificate(
278 &self,
279 id: Uuid,
280 tenant_id: Uuid,
281 cert_store: &crate::tls::CertificateStore,
282 req: UploadCertificateRequest,
283 ) -> ProxyResult<Domain> {
284 let domain = db::get_domain_for_tenant(&self.pool, id, tenant_id)
285 .await?
286 .ok_or_else(|| ProxyError::NotFound("Domain not found".into()))?;
287
288 if domain.verification_status != VerificationStatus::Verified {
289 return Err(ProxyError::Validation(
290 "Domain must be verified before a certificate can be uploaded".into(),
291 ));
292 }
293
294 let facts = crate::tls::validate_for_domain(
295 &domain.domain,
296 req.cert_pem.as_bytes(),
297 req.key_pem.as_bytes(),
298 )?;
299 verify_chain(
300 &self.policy.upload_trust,
301 &domain.domain,
302 req.cert_pem.as_bytes(),
303 )?;
304
305 cert_store
306 .save(crate::tls::Certificate {
307 domain: domain.domain.clone(),
308 cert_pem: req.cert_pem.into_bytes(),
309 key_pem: req.key_pem.into_bytes(),
310 expires_at: Some(facts.expires_at),
311 })
312 .await?;
313
314 let Some(updated) =
319 db::record_uploaded_certificate(&self.pool, id, tenant_id, facts.expires_at).await?
320 else {
321 if let Err(error) = cert_store.remove(&domain.domain).await {
322 tracing::error!(
323 domain = %domain.domain,
324 %error,
325 "could not remove a certificate uploaded for a domain deleted mid-upload"
326 );
327 }
328 return Err(ProxyError::NotFound("Domain not found".into()));
329 };
330
331 tracing::info!(
332 domain = %updated.domain,
333 expires_at = %facts.expires_at,
334 "stored an uploaded certificate"
335 );
336 Ok(updated)
337 }
338
339 pub async fn queue_ssl_issuance(&self, id: Uuid, tenant_id: Uuid) -> ProxyResult<Domain> {
351 db::queue_for_issuance(&self.pool, id, tenant_id)
352 .await?
353 .ok_or_else(|| {
354 ProxyError::NotFound(
355 "No verified domain with ssl_provider = acme for this tenant".into(),
356 )
357 })
358 }
359
360 pub async fn enable_domain(&self, id: Uuid, tenant_id: Uuid) -> ProxyResult<bool> {
362 let domain = db::get_domain_for_tenant(&self.pool, id, tenant_id)
364 .await?
365 .ok_or_else(|| ProxyError::NotFound("Domain not found".into()))?;
366
367 if domain.verification_status != VerificationStatus::Verified {
368 return Err(ProxyError::Validation(
369 "Domain must be verified before enabling".into(),
370 ));
371 }
372
373 db::enable_domain(&self.pool, id, tenant_id).await
374 }
375
376 pub async fn disable_domain(&self, id: Uuid, tenant_id: Uuid) -> ProxyResult<bool> {
378 db::disable_domain(&self.pool, id, tenant_id).await
379 }
380
381 pub async fn create_route(
387 &self,
388 domain_id: Uuid,
389 tenant_id: Uuid,
390 req: CreateDomainRouteRequest,
391 ) -> ProxyResult<DomainRoute> {
392 validation::validate_resource_name("Route", &req.name)?;
393 validation::validate_route_match(
394 &req.path_pattern,
395 &req.path_type,
396 req.methods.as_deref(),
397 )?;
398 validation::validate_add_headers(&req.add_headers)?;
399 validation::validate_remove_headers(&req.remove_headers)?;
400
401 db::create_route(&self.pool, domain_id, tenant_id, req).await
404 }
405
406 pub async fn get_route(&self, id: Uuid, tenant_id: Uuid) -> ProxyResult<Option<DomainRoute>> {
408 db::get_route_for_tenant(&self.pool, id, tenant_id).await
409 }
410
411 pub async fn list_routes_for_domain(
413 &self,
414 domain_id: Uuid,
415 tenant_id: Uuid,
416 ) -> ProxyResult<Vec<DomainRoute>> {
417 db::get_domain_for_tenant(&self.pool, domain_id, tenant_id)
419 .await?
420 .ok_or_else(|| ProxyError::NotFound("Domain not found".into()))?;
421
422 db::list_routes_for_domain(&self.pool, domain_id, tenant_id).await
423 }
424
425 pub async fn update_route(
427 &self,
428 id: Uuid,
429 tenant_id: Uuid,
430 req: UpdateDomainRouteRequest,
431 ) -> ProxyResult<Option<DomainRoute>> {
432 if let Some(name) = &req.name {
433 validation::validate_resource_name("Route", name)?;
434 }
435 if req.path_pattern.is_some() || req.path_type.is_some() || req.methods.is_some() {
436 let current = db::get_route_for_tenant(&self.pool, id, tenant_id)
439 .await?
440 .ok_or_else(|| ProxyError::NotFound("Route not found".into()))?;
441 validation::validate_route_match(
442 req.path_pattern.as_deref().unwrap_or(¤t.path_pattern),
443 req.path_type.as_ref().unwrap_or(¤t.path_type),
444 req.methods.as_deref(),
445 )?;
446 }
447 if let Some(headers) = &req.add_headers {
448 validation::validate_add_headers(headers)?;
449 }
450 if let Some(headers) = &req.remove_headers {
451 validation::validate_remove_headers(headers)?;
452 }
453
454 if let Some(upstream_id) = req.upstream_id {
456 db::get_upstream_for_tenant(&self.pool, upstream_id, tenant_id)
457 .await?
458 .ok_or_else(|| ProxyError::NotFound("Upstream not found".into()))?;
459 }
460
461 db::update_route(&self.pool, id, tenant_id, req).await
462 }
463
464 pub async fn delete_route(&self, id: Uuid, tenant_id: Uuid) -> ProxyResult<bool> {
466 db::delete_route(&self.pool, id, tenant_id).await
467 }
468
469 pub async fn create_upstream(
478 &self,
479 tenant_id: Uuid,
480 mut req: CreateUpstreamRequest,
481 ) -> ProxyResult<DomainUpstream> {
482 validation::validate_resource_name("Upstream", &req.name)?;
483 validation::validate_health_check_path(&req.health_check_path)?;
484 for backend in &mut req.backends {
485 check_backend(backend)?;
486 }
487 db::create_upstream(&self.pool, tenant_id, req).await
488 }
489
490 pub async fn get_upstream(
492 &self,
493 id: Uuid,
494 tenant_id: Uuid,
495 ) -> ProxyResult<Option<DomainUpstream>> {
496 db::get_upstream_for_tenant(&self.pool, id, tenant_id).await
497 }
498
499 pub async fn list_upstreams(&self, tenant_id: Uuid) -> ProxyResult<Vec<DomainUpstream>> {
501 db::list_upstreams(&self.pool, tenant_id).await
502 }
503
504 pub async fn update_upstream(
506 &self,
507 id: Uuid,
508 tenant_id: Uuid,
509 req: UpdateUpstreamRequest,
510 ) -> ProxyResult<Option<DomainUpstream>> {
511 if let Some(name) = &req.name {
512 validation::validate_resource_name("Upstream", name)?;
513 }
514 if let Some(path) = &req.health_check_path {
515 validation::validate_health_check_path(path)?;
516 }
517 db::update_upstream(&self.pool, id, tenant_id, req).await
518 }
519
520 pub async fn delete_upstream(&self, id: Uuid, tenant_id: Uuid) -> ProxyResult<bool> {
522 db::delete_upstream(&self.pool, id, tenant_id).await
523 }
524
525 pub async fn add_backend(
527 &self,
528 upstream_id: Uuid,
529 tenant_id: Uuid,
530 mut req: CreateBackendRequest,
531 ) -> ProxyResult<DomainBackend> {
532 check_backend(&mut req)?;
533 db::create_backend(&self.pool, upstream_id, tenant_id, req).await
534 }
535
536 pub async fn remove_backend(
538 &self,
539 backend_id: Uuid,
540 upstream_id: Uuid,
541 tenant_id: Uuid,
542 ) -> ProxyResult<bool> {
543 db::delete_backend(&self.pool, backend_id, upstream_id, tenant_id).await
544 }
545
546 pub async fn get_tenant_usage(&self, tenant_id: Uuid) -> ProxyResult<TenantUsage> {
552 db::get_tenant_usage(&self.pool, tenant_id).await
553 }
554}
555
556fn check_backend(backend: &mut CreateBackendRequest) -> ProxyResult<()> {
563 validation::validate_scheme(&backend.scheme)?;
564 backend.address = validation::validate_backend_address(&backend.address)?;
565 if !(1..=10_000).contains(&backend.weight) {
566 return Err(ProxyError::Validation(
567 "backend weight must be from 1 to 10000".into(),
568 ));
569 }
570 Ok(())
571}
572
573fn verify_chain(trust: &UploadTrust, domain: &str, cert_pem: &[u8]) -> ProxyResult<()> {
581 use rustls::client::danger::ServerCertVerifier;
582 use rustls::pki_types::{ServerName, UnixTime};
583
584 let roots = match trust {
585 UploadTrust::Unchecked => return Ok(()),
586 UploadTrust::PublicRoots => Arc::new(rustls::RootCertStore {
587 roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(),
588 }),
589 UploadTrust::Roots(roots) => roots.clone(),
590 };
591
592 let chain: Vec<_> = rustls_pemfile::certs(&mut &cert_pem[..])
593 .collect::<Result<_, _>>()
594 .map_err(|_| ProxyError::Validation("could not parse the certificate chain".into()))?;
595 let (leaf, intermediates) = chain
596 .split_first()
597 .ok_or_else(|| ProxyError::Validation("the certificate chain is empty".into()))?;
598 let name = ServerName::try_from(domain.to_string())
599 .map_err(|_| ProxyError::Validation("the domain is not a valid server name".into()))?;
600
601 let verifier = rustls::client::WebPkiServerVerifier::builder_with_provider(
602 roots,
603 Arc::new(rustls::crypto::aws_lc_rs::default_provider()),
604 )
605 .build()
606 .map_err(|e| ProxyError::Tls(format!("could not build a certificate verifier: {e}")))?;
607
608 verifier
609 .verify_server_cert(leaf, intermediates, &name, &[], UnixTime::now())
610 .map_err(|error| {
611 tracing::info!(%domain, %error, "refused an uploaded certificate chain");
612 ProxyError::Validation(
613 "the certificate chain is not trusted: it must be issued by a trusted \
614 certificate authority and include any intermediate certificates"
615 .into(),
616 )
617 })?;
618 Ok(())
619}
620
621fn generate_verification_token() -> String {
623 const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyz0123456789";
624 let mut rng = rand::rng();
625
626 (0..32)
627 .map(|_| {
628 let idx = rng.random_range(0..CHARSET.len());
629 CHARSET[idx] as char
630 })
631 .collect()
632}
633
634#[cfg(test)]
635mod tests {
636 use super::*;
637
638 #[test]
642 fn test_generate_verification_token() {
643 let token = generate_verification_token();
644 assert_eq!(token.len(), 32);
645 assert!(token.chars().all(|c| c.is_alphanumeric()));
646 }
647}