ic_custom_domains_backend/
lib.rs1mod backend_service;
2mod handlers;
3mod metrics;
4mod models;
5#[cfg(feature = "openapi")]
6mod openapi;
7pub mod router;
8
9use std::{
10 net::{IpAddr, Ipv4Addr, SocketAddr},
11 sync::Arc,
12};
13
14use anyhow::{anyhow, Context};
15use axum::Router;
16use base64::{prelude::BASE64_STANDARD, Engine};
17use chacha20poly1305::Key;
18use ic_bn_lib::{
19 ic_agent::{identity::Secp256k1Identity, Agent},
20 reqwest,
21 tls::acme::instant_acme::AccountCredentials,
22};
23use ic_bn_lib_common::types::dns::Options as DnsOptions;
24use ic_custom_domains_base::{
25 cli::CustomDomainsCli,
26 types::{
27 acme::AcmeClientConfig,
28 cipher::CertificateCipher,
29 validator::Validator,
30 worker::{Worker, WorkerConfig, WorkerMetrics},
31 },
32};
33use ic_custom_domains_canister_client::canister_client::CanisterClient;
34use prometheus::Registry;
35use tokio::fs;
36use tokio_util::sync::CancellationToken;
37
38use crate::router::{create_router, RateLimitConfig};
39
40pub async fn setup(
43 cli: &CustomDomainsCli,
44 dns_opts: DnsOptions,
45 token: CancellationToken,
46 hostname: &str,
47 metrics_registry: Registry,
48 rate_limiter_bypass_token: Option<String>,
49) -> Result<(Vec<Worker>, Router, Arc<CanisterClient>), anyhow::Error> {
50 let cipher = {
51 let key = BASE64_STANDARD
52 .decode(&cli.custom_domains_encryption_key)
53 .context("unable to decode base64 encryption key")?;
54
55 if key.len() != 32 {
56 return Err(anyhow!("encryption key must be exactly 32 bytes long"));
57 }
58
59 let key = Key::from_slice(&key);
60 let cipher = CertificateCipher::new(key);
61 Arc::new(cipher)
62 };
63
64 let agent = {
65 let key = fs::read(&cli.custom_domains_ic_identity)
66 .await
67 .context("unable to read identity from file")?;
68 let identity =
69 Secp256k1Identity::from_pem(key.as_slice()).context("failed to create IC identity")?;
70
71 let client = reqwest::ClientBuilder::new()
72 .resolve(
73 &cli.custom_domains_ic_domain.to_string(),
74 SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0),
75 )
76 .build()
77 .context("unable to build Reqwest client")?;
78
79 let agent = Agent::builder()
80 .with_identity(identity)
81 .with_url(format!("https://{}", cli.custom_domains_ic_domain))
82 .with_arc_http_middleware(Arc::new(client))
83 .build()?;
84
85 if let Some(path) = &cli.custom_domains_ic_root_key {
86 let root_key = fs::read(path).await.context("unable to read IC root key")?;
87 agent.set_root_key(root_key);
88 }
89
90 agent
91 };
92
93 let validator = {
94 Arc::new(
95 Validator::new(
96 cli.custom_domains_delegation_domain.clone(),
97 cli.custom_domains_validation_domains.clone(),
98 dns_opts.clone(),
99 )
100 .context("unable to create validator")?,
101 )
102 };
103
104 let repository = Arc::new(CanisterClient::new(
105 agent,
106 cli.custom_domains_canister_id,
107 cipher,
108 cli.custom_domains_canister_poll_interval,
109 cli.custom_domains_canister_refresh_interval,
110 ));
111
112 let acme_client = {
113 let creds = fs::read(&cli.custom_domains_acme_account)
114 .await
115 .context("unable to read ACME credentials from disk")?;
116 let creds: AccountCredentials =
117 serde_json::from_slice(&creds).context("unable to parse ACME credentials as JSON")?;
118
119 let cfg = AcmeClientConfig::new(cli.custom_domains_cloudflare_token.clone())
120 .with_acme_url(cli.custom_domains_acme_url.clone())
121 .with_credentials(creds)
122 .with_cloudflare_url(cli.custom_domains_cloudflare_url.clone())
123 .with_delegation_domain(cli.custom_domains_delegation_domain.to_string())
124 .with_dns_options(dns_opts);
125
126 Arc::new(cfg.build().await.context("unable to build ACME client")?)
127 };
128
129 let metrics = Arc::new(WorkerMetrics::new(&metrics_registry));
130 let mut workers = vec![];
131
132 for i in 0..cli.custom_domains_workers_count {
133 let worker = Worker::new(
134 format!("{hostname}-{i}"),
135 repository.clone(),
136 validator.clone(),
137 acme_client.clone(),
138 WorkerConfig::default(),
139 metrics.clone(),
140 token.clone(),
141 );
142
143 workers.push(worker);
144 }
145
146 let router = create_router(
147 repository.clone(),
148 validator,
149 metrics_registry,
150 RateLimitConfig::default(),
151 false,
152 rate_limiter_bypass_token,
153 );
154
155 Ok((workers, router, repository))
156}