Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions certbot/src/acme_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ pub struct AcmeClient {
credentials: Credentials,
dns01_client: Dns01Client,
max_dns_wait: Duration,
/// TTL for DNS TXT records used in ACME challenges (in seconds).
dns_txt_ttl: u32,
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -58,6 +60,7 @@ impl AcmeClient {
dns01_client: Dns01Client,
encoded_credentials: &str,
max_dns_wait: Duration,
dns_txt_ttl: u32,
) -> Result<Self> {
let credentials: Credentials = serde_json::from_str(encoded_credentials)?;
let account = Account::from_credentials(credentials.credentials).await?;
Expand All @@ -67,6 +70,7 @@ impl AcmeClient {
dns01_client,
credentials,
max_dns_wait,
dns_txt_ttl,
})
}

Expand All @@ -75,6 +79,7 @@ impl AcmeClient {
acme_url: &str,
dns01_client: Dns01Client,
max_dns_wait: Duration,
dns_txt_ttl: u32,
) -> Result<Self> {
let (account, credentials) = Account::create(
&NewAccount {
Expand All @@ -97,6 +102,7 @@ impl AcmeClient {
dns01_client,
credentials,
max_dns_wait,
dns_txt_ttl,
})
}

Expand Down Expand Up @@ -328,10 +334,13 @@ impl AcmeClient {
.remove_txt_records(&acme_domain)
.await
.context("failed to remove existing dns record")?;
debug!("creating TXT record for {acme_domain}");
debug!(
"creating TXT record for {acme_domain} with TTL {}s",
self.dns_txt_ttl
);
let id = self
.dns01_client
.add_txt_record(&acme_domain, &dns_value)
.add_txt_record(&acme_domain, &dns_value, self.dns_txt_ttl)
.await
.context("failed to create dns record")?;
challenges.push(Challenge {
Expand Down
23 changes: 19 additions & 4 deletions certbot/src/bot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ pub struct CertBotConfig {
renew_expires_in: Duration,
renewed_hook: Option<String>,
max_dns_wait: Duration,
/// TTL for DNS TXT records used in ACME challenges (in seconds).
/// Minimum is 60 for Cloudflare.
#[builder(default = 60)]
dns_txt_ttl: u32,
}

impl CertBotConfig {
Expand All @@ -55,9 +59,14 @@ async fn create_new_account(
dns01_client: Dns01Client,
) -> Result<AcmeClient> {
info!("creating new ACME account");
let client = AcmeClient::new_account(&config.acme_url, dns01_client, config.max_dns_wait)
.await
.context("failed to create new account")?;
let client = AcmeClient::new_account(
&config.acme_url,
dns01_client,
config.max_dns_wait,
config.dns_txt_ttl,
)
.await
.context("failed to create new account")?;
let credentials = client
.dump_credentials()
.context("failed to dump credentials")?;
Expand Down Expand Up @@ -90,7 +99,13 @@ impl CertBot {
let acme_client = match fs::read_to_string(&config.credentials_file) {
Ok(credentials) => {
if acme_matches(&credentials, &config.acme_url) {
AcmeClient::load(dns01_client, &credentials, config.max_dns_wait).await?
AcmeClient::load(
dns01_client,
&credentials,
config.max_dns_wait,
config.dns_txt_ttl,
)
.await?
} else {
create_new_account(&config, dns01_client).await?
}
Expand Down
3 changes: 2 additions & 1 deletion certbot/src/dns01_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ pub(crate) trait Dns01Api {
/// Creates a TXT DNS record with the given domain and content.
///
/// Returns the ID of the created record.
async fn add_txt_record(&self, domain: &str, content: &str) -> Result<String>;
/// The `ttl` parameter specifies the time-to-live in seconds (1 = auto, min 60 for Cloudflare).
async fn add_txt_record(&self, domain: &str, content: &str, ttl: u32) -> Result<String>;

/// Add a CAA record for the given domain.
async fn add_caa_record(
Expand Down
7 changes: 4 additions & 3 deletions certbot/src/dns01_client/cloudflare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,12 +270,13 @@ impl Dns01Api for CloudflareClient {
Ok(())
}

async fn add_txt_record(&self, domain: &str, content: &str) -> Result<String> {
async fn add_txt_record(&self, domain: &str, content: &str, ttl: u32) -> Result<String> {
let response = self
.add_record(&json!({
"type": "TXT",
"name": domain,
"content": content,
"ttl": ttl,
}))
.await?;
Ok(response.result.id)
Expand Down Expand Up @@ -358,7 +359,7 @@ mod tests {
let subdomain = random_subdomain();
println!("subdomain: {}", subdomain);
let record_id = client
.add_txt_record(&subdomain, "1234567890")
.add_txt_record(&subdomain, "1234567890", 60)
.await
.unwrap();
let record = client.get_txt_records(&subdomain).await.unwrap();
Expand All @@ -375,7 +376,7 @@ mod tests {
let subdomain = random_subdomain();
println!("subdomain: {}", subdomain);
let record_id = client
.add_txt_record(&subdomain, "1234567890")
.add_txt_record(&subdomain, "1234567890", 60)
.await
.unwrap();
let record = client.get_txt_records(&subdomain).await.unwrap();
Expand Down
1 change: 1 addition & 0 deletions gateway/dstack-app/builder/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ renew_interval = "1h"
renew_before_expiration = "10d"
renew_timeout = "5m"
max_dns_wait = "${CERTBOT_MAX_DNS_WAIT:-5m}"
dns_txt_ttl = "${CERTBOT_DNS_TXT_TTL:-60}"

[core.wg]
public_key = "$PUBLIC_KEY"
Expand Down
2 changes: 2 additions & 0 deletions gateway/dstack-app/deploy-to-vmm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ GUEST_AGENT_ADDR=127.0.0.1:9206
WG_ADDR=0.0.0.0:9202

CERTBOT_MAX_DNS_WAIT=5m
CERTBOT_DNS_TXT_TTL=60

# The token used to launch the App
APP_LAUNCH_TOKEN=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)
Expand Down Expand Up @@ -141,6 +142,7 @@ SUBNET_INDEX=$SUBNET_INDEX
APP_LAUNCH_TOKEN=$APP_LAUNCH_TOKEN
RPC_DOMAIN=$RPC_DOMAIN
CERTBOT_MAX_DNS_WAIT=$CERTBOT_MAX_DNS_WAIT
CERTBOT_DNS_TXT_TTL=$CERTBOT_DNS_TXT_TTL
EOF

if [ -n "$APP_COMPOSE_FILE" ]; then
Expand Down
3 changes: 3 additions & 0 deletions gateway/gateway.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ renew_interval = "1h"
renew_before_expiration = "10d"
renew_timeout = "120s"
max_dns_wait = "5m"
# TTL for DNS TXT records used in ACME challenges (in seconds).
# Minimum is 60 for Cloudflare.
dns_txt_ttl = 60

[core.wg]
public_key = ""
Expand Down
9 changes: 9 additions & 0 deletions gateway/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,14 @@ pub struct CertbotConfig {
/// Maximum time to wait for DNS propagation
#[serde(with = "serde_duration")]
pub max_dns_wait: Duration,
/// TTL for DNS TXT records used in ACME challenges (in seconds).
/// Minimum is 60 for Cloudflare. Lower TTL means faster DNS propagation.
#[serde(default = "default_dns_txt_ttl")]
pub dns_txt_ttl: u32,
}

fn default_dns_txt_ttl() -> u32 {
60
}

impl CertbotConfig {
Expand All @@ -228,6 +236,7 @@ impl CertbotConfig {
.renew_expires_in(self.renew_before_expiration)
.auto_set_caa(self.auto_set_caa)
.max_dns_wait(self.max_dns_wait)
.dns_txt_ttl(self.dns_txt_ttl)
.build()
}

Expand Down
Loading