Skip to content

Commit 3917376

Browse files
authored
Merge pull request #436 from Evrard-Nil/feat/make-txt-record-ttl-configurable-
Make DNS TXT record TTL configurable.
2 parents b914214 + 696441b commit 3917376

File tree

8 files changed

+51
-10
lines changed

8 files changed

+51
-10
lines changed

certbot/src/acme_client.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ pub struct AcmeClient {
2828
credentials: Credentials,
2929
dns01_client: Dns01Client,
3030
max_dns_wait: Duration,
31+
/// TTL for DNS TXT records used in ACME challenges (in seconds).
32+
dns_txt_ttl: u32,
3133
}
3234

3335
#[derive(Debug, Clone)]
@@ -58,6 +60,7 @@ impl AcmeClient {
5860
dns01_client: Dns01Client,
5961
encoded_credentials: &str,
6062
max_dns_wait: Duration,
63+
dns_txt_ttl: u32,
6164
) -> Result<Self> {
6265
let credentials: Credentials = serde_json::from_str(encoded_credentials)?;
6366
let account = Account::from_credentials(credentials.credentials).await?;
@@ -67,6 +70,7 @@ impl AcmeClient {
6770
dns01_client,
6871
credentials,
6972
max_dns_wait,
73+
dns_txt_ttl,
7074
})
7175
}
7276

@@ -75,6 +79,7 @@ impl AcmeClient {
7579
acme_url: &str,
7680
dns01_client: Dns01Client,
7781
max_dns_wait: Duration,
82+
dns_txt_ttl: u32,
7883
) -> Result<Self> {
7984
let (account, credentials) = Account::create(
8085
&NewAccount {
@@ -97,6 +102,7 @@ impl AcmeClient {
97102
dns01_client,
98103
credentials,
99104
max_dns_wait,
105+
dns_txt_ttl,
100106
})
101107
}
102108

@@ -328,10 +334,13 @@ impl AcmeClient {
328334
.remove_txt_records(&acme_domain)
329335
.await
330336
.context("failed to remove existing dns record")?;
331-
debug!("creating TXT record for {acme_domain}");
337+
debug!(
338+
"creating TXT record for {acme_domain} with TTL {}s",
339+
self.dns_txt_ttl
340+
);
332341
let id = self
333342
.dns01_client
334-
.add_txt_record(&acme_domain, &dns_value)
343+
.add_txt_record(&acme_domain, &dns_value, self.dns_txt_ttl)
335344
.await
336345
.context("failed to create dns record")?;
337346
challenges.push(Challenge {

certbot/src/bot.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ pub struct CertBotConfig {
3737
renew_expires_in: Duration,
3838
renewed_hook: Option<String>,
3939
max_dns_wait: Duration,
40+
/// TTL for DNS TXT records used in ACME challenges (in seconds).
41+
/// Minimum is 60 for Cloudflare.
42+
#[builder(default = 60)]
43+
dns_txt_ttl: u32,
4044
}
4145

4246
impl CertBotConfig {
@@ -55,9 +59,14 @@ async fn create_new_account(
5559
dns01_client: Dns01Client,
5660
) -> Result<AcmeClient> {
5761
info!("creating new ACME account");
58-
let client = AcmeClient::new_account(&config.acme_url, dns01_client, config.max_dns_wait)
59-
.await
60-
.context("failed to create new account")?;
62+
let client = AcmeClient::new_account(
63+
&config.acme_url,
64+
dns01_client,
65+
config.max_dns_wait,
66+
config.dns_txt_ttl,
67+
)
68+
.await
69+
.context("failed to create new account")?;
6170
let credentials = client
6271
.dump_credentials()
6372
.context("failed to dump credentials")?;
@@ -90,7 +99,13 @@ impl CertBot {
9099
let acme_client = match fs::read_to_string(&config.credentials_file) {
91100
Ok(credentials) => {
92101
if acme_matches(&credentials, &config.acme_url) {
93-
AcmeClient::load(dns01_client, &credentials, config.max_dns_wait).await?
102+
AcmeClient::load(
103+
dns01_client,
104+
&credentials,
105+
config.max_dns_wait,
106+
config.dns_txt_ttl,
107+
)
108+
.await?
94109
} else {
95110
create_new_account(&config, dns01_client).await?
96111
}

certbot/src/dns01_client.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ pub(crate) trait Dns01Api {
2828
/// Creates a TXT DNS record with the given domain and content.
2929
///
3030
/// Returns the ID of the created record.
31-
async fn add_txt_record(&self, domain: &str, content: &str) -> Result<String>;
31+
/// The `ttl` parameter specifies the time-to-live in seconds (1 = auto, min 60 for Cloudflare).
32+
async fn add_txt_record(&self, domain: &str, content: &str, ttl: u32) -> Result<String>;
3233

3334
/// Add a CAA record for the given domain.
3435
async fn add_caa_record(

certbot/src/dns01_client/cloudflare.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -270,12 +270,13 @@ impl Dns01Api for CloudflareClient {
270270
Ok(())
271271
}
272272

273-
async fn add_txt_record(&self, domain: &str, content: &str) -> Result<String> {
273+
async fn add_txt_record(&self, domain: &str, content: &str, ttl: u32) -> Result<String> {
274274
let response = self
275275
.add_record(&json!({
276276
"type": "TXT",
277277
"name": domain,
278278
"content": content,
279+
"ttl": ttl,
279280
}))
280281
.await?;
281282
Ok(response.result.id)
@@ -358,7 +359,7 @@ mod tests {
358359
let subdomain = random_subdomain();
359360
println!("subdomain: {}", subdomain);
360361
let record_id = client
361-
.add_txt_record(&subdomain, "1234567890")
362+
.add_txt_record(&subdomain, "1234567890", 60)
362363
.await
363364
.unwrap();
364365
let record = client.get_txt_records(&subdomain).await.unwrap();
@@ -375,7 +376,7 @@ mod tests {
375376
let subdomain = random_subdomain();
376377
println!("subdomain: {}", subdomain);
377378
let record_id = client
378-
.add_txt_record(&subdomain, "1234567890")
379+
.add_txt_record(&subdomain, "1234567890", 60)
379380
.await
380381
.unwrap();
381382
let record = client.get_txt_records(&subdomain).await.unwrap();

gateway/dstack-app/builder/entrypoint.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ renew_interval = "1h"
118118
renew_before_expiration = "10d"
119119
renew_timeout = "5m"
120120
max_dns_wait = "${CERTBOT_MAX_DNS_WAIT:-5m}"
121+
dns_txt_ttl = "${CERTBOT_DNS_TXT_TTL:-60}"
121122
122123
[core.wg]
123124
public_key = "$PUBLIC_KEY"

gateway/dstack-app/deploy-to-vmm.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ GUEST_AGENT_ADDR=127.0.0.1:9206
8282
WG_ADDR=0.0.0.0:9202
8383
8484
CERTBOT_MAX_DNS_WAIT=5m
85+
CERTBOT_DNS_TXT_TTL=60
8586
8687
# The token used to launch the App
8788
APP_LAUNCH_TOKEN=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)
@@ -141,6 +142,7 @@ SUBNET_INDEX=$SUBNET_INDEX
141142
APP_LAUNCH_TOKEN=$APP_LAUNCH_TOKEN
142143
RPC_DOMAIN=$RPC_DOMAIN
143144
CERTBOT_MAX_DNS_WAIT=$CERTBOT_MAX_DNS_WAIT
145+
CERTBOT_DNS_TXT_TTL=$CERTBOT_DNS_TXT_TTL
144146
EOF
145147

146148
if [ -n "$APP_COMPOSE_FILE" ]; then

gateway/gateway.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ renew_interval = "1h"
3838
renew_before_expiration = "10d"
3939
renew_timeout = "120s"
4040
max_dns_wait = "5m"
41+
# TTL for DNS TXT records used in ACME challenges (in seconds).
42+
# Minimum is 60 for Cloudflare.
43+
dns_txt_ttl = 60
4144

4245
[core.wg]
4346
public_key = ""

gateway/src/config.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,14 @@ pub struct CertbotConfig {
209209
/// Maximum time to wait for DNS propagation
210210
#[serde(with = "serde_duration")]
211211
pub max_dns_wait: Duration,
212+
/// TTL for DNS TXT records used in ACME challenges (in seconds).
213+
/// Minimum is 60 for Cloudflare. Lower TTL means faster DNS propagation.
214+
#[serde(default = "default_dns_txt_ttl")]
215+
pub dns_txt_ttl: u32,
216+
}
217+
218+
fn default_dns_txt_ttl() -> u32 {
219+
60
212220
}
213221

214222
impl CertbotConfig {
@@ -228,6 +236,7 @@ impl CertbotConfig {
228236
.renew_expires_in(self.renew_before_expiration)
229237
.auto_set_caa(self.auto_set_caa)
230238
.max_dns_wait(self.max_dns_wait)
239+
.dns_txt_ttl(self.dns_txt_ttl)
231240
.build()
232241
}
233242

0 commit comments

Comments
 (0)