Skip to content

Commit 7019a88

Browse files
committed
*) mod_md: update to version 2.6.1
- Increasing default `MDRetryDelay` to 30 seconds to generate less bursty traffic on errored renewals for the ACME CA. This leads to error retries of 30s, 1 minute, 2, 4, etc. up to daily attempts. - Checking that configuring `MDRetryDelay` will result in a positive duration. A delay of 0 is not accepted. - Fix a bug in checking Content-Type of responses from the ACME server. - Added ACME ARI support (rfc9773) to the module. Enabled by default. New directive "MDRenewViaARI on|off" for controlling this. - Removing tailscale support. It has not been working for a long time as the company decided to change their APIs. Away with the dead code, documentation and tests. - Fixed a compilation issue with pre-industrial versions of libcurl. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1927807 13f79535-47bb-0310-9956-ffa450edef68
1 parent 2cf135c commit 7019a88

36 files changed

+660
-700
lines changed

changes-entries/md_v2.6.1.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
*) mod_md: update to version 2.6.1
2+
- Increasing default `MDRetryDelay` to 30 seconds to generate less bursty
3+
traffic on errored renewals for the ACME CA. This leads to error retries
4+
of 30s, 1 minute, 2, 4, etc. up to daily attempts.
5+
- Checking that configuring `MDRetryDelay` will result in a positive
6+
duration. A delay of 0 is not accepted.
7+
- Fix a bug in checking Content-Type of responses from the ACME server.
8+
- Added ACME ARI support (rfc9773) to the module. Enabled by default. New
9+
directive "MDRenewViaARI on|off" for controlling this.
10+
- Removing tailscale support. It has not been working for a long time
11+
as the company decided to change their APIs. Away with the dead code,
12+
documentation and tests.
13+
- Fixed a compilation issue with pre-industrial versions of libcurl.

docs/manual/mod/mod_md.xml

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1393,7 +1393,7 @@ MDMessageCmd /etc/apache/md-message
13931393
<name>MDRetryDelay</name>
13941394
<description>Time length for first retry, doubled on every consecutive error.</description>
13951395
<syntax>MDRetryDelay <var>duration</var></syntax>
1396-
<default>MDRetryDelay 5s</default>
1396+
<default>MDRetryDelay 30s</default>
13971397
<contextlist>
13981398
<context>server config</context>
13991399
</contextlist>
@@ -1408,6 +1408,10 @@ MDMessageCmd /etc/apache/md-message
14081408
It is kept separate for each certificate renewal. Meaning an error
14091409
on one MDomain does not delay the renewals of other domains.
14101410
</p>
1411+
<p>
1412+
In mod_md v2.6.1, the default delay has been increased from 5
1413+
seconds to 30.
1414+
</p>
14111415
</usage>
14121416
</directivesynopsis>
14131417

@@ -1594,4 +1598,26 @@ MDMessageCmd /etc/apache/md-message
15941598
</p>
15951599
</usage>
15961600
</directivesynopsis>
1601+
1602+
<directivesynopsis>
1603+
<name>MDRenewViaARI</name>
1604+
<description>usage of the ACME ARI extension (rfc9773).</description>
1605+
<syntax>MDRenewViaARI on|off</syntax>
1606+
<default>MDRenewViaARI on</default>
1607+
<contextlist>
1608+
<context>server config</context>
1609+
</contextlist>
1610+
<usage>
1611+
<p>
1612+
En-/Disable certificate renewals triggered via the ACME ARI
1613+
extension (rfc9773). These renewals happen *in addition* to
1614+
the mechanism controlled by <directive>MDRenewWindow</directive>.
1615+
</p><p>
1616+
ACME ARI allows an ACME CA to somewhat shape incoming renewal
1617+
traffic. More importantly though, it can inform clients of
1618+
urgent renewals, e.g. when a certificate or part of its chain
1619+
has been revoked.
1620+
</p>
1621+
</usage>
1622+
</directivesynopsis>
15971623
</modulesynopsis>

modules/md/config2.m4

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,6 @@ md_reg.lo dnl
156156
md_status.lo dnl
157157
md_store.lo dnl
158158
md_store_fs.lo dnl
159-
md_tailscale.lo dnl
160159
md_time.lo dnl
161160
md_util.lo dnl
162161
mod_md.lo dnl

modules/md/md.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ struct md_t {
9494
const char *ca_eab_hmac; /* optional HMAC for external account binding */
9595
const char *profile; /* optional cert profile to order */
9696
int profile_mandatory; /* if profile, when given, is mandatory */
97+
int ari_renewals; /* if ACME ARI (RFC 9773) can trigger renewals */
9798

9899
const char *state_descr; /* description of state of NULL */
99100

@@ -119,6 +120,8 @@ struct md_t {
119120
#define MD_KEY_ACTIVATION_DELAY "activation-delay"
120121
#define MD_KEY_ACTIVITY "activity"
121122
#define MD_KEY_AGREEMENT "agreement"
123+
#define MD_KEY_ARI_CERT_ID "ari-cert-id"
124+
#define MD_KEY_ARI_RENEWALS "ari-renewals"
122125
#define MD_KEY_AUTHORIZATIONS "authorizations"
123126
#define MD_KEY_BITS "bits"
124127
#define MD_KEY_CA "ca"

modules/md/md_acme.c

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ static apr_status_t acmev2_GET_as_POST_init(md_acme_req_t *req, void *baton)
332332
return md_acme_req_body_init(req, NULL);
333333
}
334334

335-
static apr_status_t md_acme_req_send(md_acme_req_t *req)
335+
static apr_status_t md_acme_req_send(md_acme_req_t *req, int get_as_post)
336336
{
337337
apr_status_t rv;
338338
md_acme_t *acme = req->acme;
@@ -352,7 +352,7 @@ static apr_status_t md_acme_req_send(md_acme_req_t *req)
352352
if (APR_SUCCESS != rv) goto leave;
353353
}
354354

355-
if (!strcmp("GET", req->method) && !req->on_init && !req->req_json) {
355+
if (get_as_post && !strcmp("GET", req->method) && !req->on_init && !req->req_json) {
356356
/* See <https://ietf-wg-acme.github.io/acme/draft-ietf-acme-acme.html#rfc.section.6.3>
357357
* and <https://mailarchive.ietf.org/arch/msg/acme/sotffSQ0OWV-qQJodLwWYWcEVKI>
358358
* and <https://community.letsencrypt.org/t/acme-v2-scheduled-deprecation-of-unauthenticated-resource-gets/74380>
@@ -420,7 +420,7 @@ static apr_status_t md_acme_req_send(md_acme_req_t *req)
420420

421421
if (APR_EAGAIN == rv && req->max_retries > 0) {
422422
--req->max_retries;
423-
rv = md_acme_req_send(req);
423+
rv = md_acme_req_send(req, 1);
424424
}
425425
req = NULL;
426426

@@ -449,14 +449,15 @@ apr_status_t md_acme_POST(md_acme_t *acme, const char *url,
449449
req->on_err = on_err;
450450
req->baton = baton;
451451

452-
return md_acme_req_send(req);
452+
return md_acme_req_send(req, 1);
453453
}
454454

455455
apr_status_t md_acme_GET(md_acme_t *acme, const char *url,
456456
md_acme_req_init_cb *on_init,
457457
md_acme_req_json_cb *on_json,
458458
md_acme_req_res_cb *on_res,
459-
md_acme_req_err_cb *on_err,
459+
md_acme_req_err_cb *on_err,
460+
int get_as_post,
460461
void *baton)
461462
{
462463
md_acme_req_t *req;
@@ -472,7 +473,7 @@ apr_status_t md_acme_GET(md_acme_t *acme, const char *url,
472473
req->on_err = on_err;
473474
req->baton = baton;
474475

475-
return md_acme_req_send(req);
476+
return md_acme_req_send(req, get_as_post);
476477
}
477478

478479
void md_acme_report_result(md_acme_t *acme, apr_status_t rv, struct md_result_t *result)
@@ -507,15 +508,15 @@ static apr_status_t on_got_json(md_acme_t *acme, apr_pool_t *p, const apr_table_
507508
}
508509

509510
apr_status_t md_acme_get_json(struct md_json_t **pjson, md_acme_t *acme,
510-
const char *url, apr_pool_t *p)
511+
const char *url, int get_as_post, apr_pool_t *p)
511512
{
512513
apr_status_t rv;
513514
json_ctx ctx;
514515

515516
ctx.pool = p;
516517
ctx.json = NULL;
517518

518-
rv = md_acme_GET(acme, url, NULL, on_got_json, NULL, NULL, &ctx);
519+
rv = md_acme_GET(acme, url, NULL, on_got_json, NULL, NULL, get_as_post, &ctx);
519520
*pjson = (APR_SUCCESS == rv)? ctx.json : NULL;
520521
return rv;
521522
}
@@ -720,6 +721,7 @@ static apr_status_t update_directory(const md_http_response_t *res, void *data)
720721
acme->api.v2.revoke_cert = md_json_dups(acme->p, json, "revokeCert", NULL);
721722
acme->api.v2.key_change = md_json_dups(acme->p, json, "keyChange", NULL);
722723
acme->api.v2.new_nonce = md_json_dups(acme->p, json, "newNonce", NULL);
724+
acme->api.v2.renewal_info = md_json_dups(acme->p, json, "renewalInfo", NULL);
723725
/* RFC 8555 only requires "directory" and "newNonce" resources.
724726
* mod_md uses "newAccount" and "newOrder" so check for them.
725727
* But mod_md does not use the "revokeCert" or "keyChange"

modules/md/md_acme.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ struct md_acme_t {
118118
const char *key_change;
119119
const char *revoke_cert;
120120
const char *new_nonce;
121+
const char *renewal_info;
121122
struct apr_array_header_t *profiles;
122123
} v2;
123124
} api;
@@ -275,6 +276,7 @@ apr_status_t md_acme_GET(md_acme_t *acme, const char *url,
275276
md_acme_req_json_cb *on_json,
276277
md_acme_req_res_cb *on_res,
277278
md_acme_req_err_cb *on_err,
279+
int get_as_post,
278280
void *baton);
279281
/**
280282
* Perform a POST against the ACME url. If a on_json callback is given and
@@ -301,7 +303,7 @@ apr_status_t md_acme_POST(md_acme_t *acme, const char *url,
301303
* Retrieve a JSON resource from the ACME server
302304
*/
303305
apr_status_t md_acme_get_json(struct md_json_t **pjson, md_acme_t *acme,
304-
const char *url, apr_pool_t *p);
306+
const char *url, int get_as_post, apr_pool_t *p);
305307

306308

307309
apr_status_t md_acme_req_body_init(md_acme_req_t *req, struct md_json_t *jpayload);

modules/md/md_acme_authz.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ apr_status_t md_acme_authz_update(md_acme_authz_t *authz, md_acme_t *acme, apr_p
131131
err = "unable to parse response";
132132
log_level = MD_LOG_ERR;
133133

134-
if (APR_SUCCESS == (rv = md_acme_get_json(&json, acme, authz->url, p))
134+
if (APR_SUCCESS == (rv = md_acme_get_json(&json, acme, authz->url, 1, p))
135135
&& (s = md_json_gets(json, MD_KEY_STATUS, NULL))) {
136136

137137
authz->domain = md_json_gets(json, MD_KEY_IDENTIFIER, MD_KEY_VALUE, NULL);

modules/md/md_acme_drive.c

Lines changed: 149 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ static apr_status_t get_cert(void *baton, int attempt)
256256
(void)attempt;
257257
md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, d->p, "retrieving cert from %s",
258258
ad->order->certificate);
259-
return md_acme_GET(ad->acme, ad->order->certificate, NULL, NULL, on_add_cert, NULL, d);
259+
return md_acme_GET(ad->acme, ad->order->certificate, NULL, NULL, on_add_cert, NULL, 1, d);
260260
}
261261

262262
apr_status_t md_acme_drive_cert_poll(md_proto_driver_t *d, int only_once)
@@ -429,7 +429,7 @@ static apr_status_t get_chain(void *baton, int attempt)
429429

430430
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
431431
"next chain cert at %s", ad->chain_up_link);
432-
rv = md_acme_GET(ad->acme, ad->chain_up_link, NULL, NULL, on_add_chain, NULL, d);
432+
rv = md_acme_GET(ad->acme, ad->chain_up_link, NULL, NULL, on_add_chain, NULL, 1, d);
433433

434434
if (APR_SUCCESS == rv && nelts == ad->cred->chain->nelts) {
435435
break;
@@ -1094,10 +1094,155 @@ static apr_status_t acme_complete_md(md_t *md, apr_pool_t *p)
10941094
return APR_SUCCESS;
10951095
}
10961096

1097+
static apr_status_t acme_get_ari(md_proto_driver_t *d,
1098+
struct md_result_t *result,
1099+
apr_time_t *prenew_at,
1100+
const char **purl)
1101+
{
1102+
md_acme_driver_t *ad = d->baton;
1103+
apr_status_t rv = APR_SUCCESS;
1104+
const char *ca_effective = NULL;
1105+
apr_array_header_t *certs;
1106+
const md_cert_t *cert;
1107+
int i;
1108+
1109+
*prenew_at = 0;
1110+
*purl = NULL;
1111+
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
1112+
"get ARI status for %s", d->md->name);
1113+
1114+
if (d->md->cert_files && d->md->cert_files->nelts) {
1115+
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
1116+
"%s is configured with static files, no ARI", d->md->name);
1117+
goto out;
1118+
}
1119+
1120+
if (!d->md->ca_urls || d->md->ca_urls->nelts <= 0) {
1121+
/* No CA defined? This is checked in several other places, but lets be sure */
1122+
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
1123+
"%s is missing MDCertificateAuthority", d->md->name);
1124+
goto out;
1125+
}
1126+
1127+
/* always pick the first CA for this */
1128+
ca_effective = APR_ARRAY_IDX(d->md->ca_urls, 0, const char*);
1129+
1130+
certs = apr_array_make(d->p, 5, sizeof(md_cert_t*));
1131+
for (i = 0; i < md_pkeys_spec_count(d->md->pks); ++i) {
1132+
const md_pubcert_t *pubcert;
1133+
if (APR_SUCCESS == md_reg_get_pubcert(&pubcert, d->reg, d->md, i, d->p)) {
1134+
cert = APR_ARRAY_IDX(pubcert->certs, 0, const md_cert_t*);
1135+
APR_ARRAY_PUSH(certs, const md_cert_t*) = cert;
1136+
}
1137+
}
1138+
1139+
if (!certs->nelts) {
1140+
rv = APR_SUCCESS;
1141+
goto out;
1142+
}
1143+
1144+
if (APR_SUCCESS != (rv = md_acme_create(&ad->acme, d->p, ca_effective,
1145+
d->proxy_url, d->ca_file))) {
1146+
md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p,
1147+
"create ACME communications");
1148+
goto out;
1149+
}
1150+
if (APR_SUCCESS != (rv = md_acme_setup(ad->acme, result))) {
1151+
md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p,
1152+
"setup ACME communications");
1153+
goto out;
1154+
}
1155+
if (ad->acme->version != MD_ACME_VERSION_2 || !ad->acme->api.v2.renewal_info) {
1156+
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
1157+
"ARI not supported by ACME CA %s", ca_effective);
1158+
goto out;
1159+
}
1160+
1161+
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
1162+
"assessing ARI status for %d certificates", certs->nelts);
1163+
for (i = 0; i < certs->nelts; ++i) {
1164+
const char *ari_cert_id;
1165+
const char *ari_url = NULL, *ari_expl_url;
1166+
const char *renew_start, *renew_end;
1167+
apr_time_t start, end;
1168+
md_json_t *json;
1169+
unsigned char c;
1170+
1171+
cert = APR_ARRAY_IDX(certs, i, md_cert_t*);
1172+
if (md_cert_get_ari_cert_id(&ari_cert_id, cert, d->p) != APR_SUCCESS)
1173+
continue;
1174+
1175+
ari_url = apr_psprintf(d->p, "%s/%s", ad->acme->api.v2.renewal_info,
1176+
ari_cert_id);
1177+
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
1178+
"GET #%d ARI from %s", i, ari_url);
1179+
if ((rv = md_acme_get_json(&json, ad->acme, ari_url, 0, d->p)) != APR_SUCCESS) {
1180+
md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, d->p,
1181+
"error retrieving ARI from %s", ari_url);
1182+
continue;
1183+
}
1184+
1185+
if(!json) {
1186+
md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, d->p,
1187+
"ARI returned no JSON from %s", ari_url);
1188+
continue;
1189+
}
1190+
1191+
renew_start = md_json_gets(json, "suggestedWindow", "start", NULL);
1192+
renew_end = md_json_gets(json, "suggestedWindow", "end", NULL);
1193+
ari_expl_url = md_json_gets(json, "explanationURL", NULL);
1194+
if (!renew_start || !renew_end) {
1195+
md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, d->p,
1196+
"renewal info from CA incomplete for %s", ari_url);
1197+
continue;
1198+
}
1199+
start = md_time_parse_rfc3339(renew_start);
1200+
end = md_time_parse_rfc3339(renew_end);
1201+
if (!start) {
1202+
md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, d->p,
1203+
"error parsing CA renew start time: '%s'",
1204+
renew_start);
1205+
continue;
1206+
}
1207+
if (!end) {
1208+
md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, d->p,
1209+
"error parsing CA renew end time: '%s'",
1210+
renew_end);
1211+
continue;
1212+
}
1213+
if (start > end) {
1214+
md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, d->p,
1215+
"CA advises weird renewal between '%s' and '%s'",
1216+
renew_start, renew_end);
1217+
continue;
1218+
}
1219+
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
1220+
"CA advises renew via ARI between %s and %s"
1221+
" (explantion: %s)",
1222+
renew_start, renew_end,
1223+
ari_expl_url? ari_expl_url : "none given");
1224+
/* select a random value between start and end */
1225+
md_rand_bytes(&c, sizeof(c), d->p);
1226+
start += apr_time_from_sec((apr_time_sec(end - start) * (c - 128)) / 256);
1227+
if (!*prenew_at || (start < *prenew_at)) {
1228+
*prenew_at = start;
1229+
*purl = apr_pstrdup(d->p, ari_expl_url);
1230+
}
1231+
}
1232+
1233+
rv = APR_SUCCESS;
1234+
out:
1235+
return rv;
1236+
}
1237+
10971238
static md_proto_t ACME_PROTO = {
1098-
MD_PROTO_ACME, acme_driver_init, acme_driver_renew,
1099-
acme_driver_preload_init, acme_driver_preload,
1239+
MD_PROTO_ACME,
1240+
acme_driver_init,
1241+
acme_driver_renew,
1242+
acme_driver_preload_init,
1243+
acme_driver_preload,
11001244
acme_complete_md,
1245+
acme_get_ari,
11011246
};
11021247

11031248
apr_status_t md_acme_protos_add(apr_hash_t *protos, apr_pool_t *p)

0 commit comments

Comments
 (0)