Skip to main content

ic_custom_domains_backend/
lib.rs

1mod 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
40/// Sets up everything required to run Custom Domains.
41/// Returns Worker, Axum Router and a CanisterClient to access data.
42pub 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}