From 7019a88e61af97e947e251840b4e37fbd8b7b8b5 Mon Sep 17 00:00:00 2001
From: Stefan Eissing
Date: Fri, 15 Aug 2025 11:23:29 +0000
Subject: [PATCH] *) 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
---
changes-entries/md_v2.6.1.txt | 13 +
docs/manual/mod/mod_md.xml | 28 +-
modules/md/config2.m4 | 1 -
modules/md/md.h | 3 +
modules/md/md_acme.c | 18 +-
modules/md/md_acme.h | 4 +-
modules/md/md_acme_authz.c | 2 +-
modules/md/md_acme_drive.c | 153 +++++++++-
modules/md/md_acme_order.c | 24 +-
modules/md/md_acme_order.h | 2 +-
modules/md/md_acmev2_drive.c | 35 ++-
modules/md/md_core.c | 4 +
modules/md/md_crypt.c | 84 +++++-
modules/md/md_crypt.h | 7 +
modules/md/md_http.c | 12 +-
modules/md/md_json.c | 10 +-
modules/md/md_reg.c | 76 ++++-
modules/md/md_reg.h | 11 +
modules/md/md_status.c | 48 +++-
modules/md/md_tailscale.c | 383 --------------------------
modules/md/md_tailscale.h | 25 --
modules/md/md_time.c | 67 +++++
modules/md/md_time.h | 3 +
modules/md/md_version.h | 5 +-
modules/md/mod_md.c | 6 +-
modules/md/mod_md.dsp | 4 -
modules/md/mod_md_config.c | 27 +-
modules/md/mod_md_config.h | 2 +
modules/md/mod_md_drive.c | 77 +++++-
modules/md/mod_md_status.c | 6 +-
test/modules/md/md_conf.py | 2 +
test/modules/md/test_702_auto.py | 3 +-
test/modules/md/test_710_profiles.py | 2 +-
test/modules/md/test_730_static.py | 10 +
test/modules/md/test_780_tailscale.py | 198 -------------
test/modules/md/test_920_status.py | 5 +
36 files changed, 660 insertions(+), 700 deletions(-)
create mode 100644 changes-entries/md_v2.6.1.txt
delete mode 100644 modules/md/md_tailscale.c
delete mode 100644 modules/md/md_tailscale.h
delete mode 100644 test/modules/md/test_780_tailscale.py
diff --git a/changes-entries/md_v2.6.1.txt b/changes-entries/md_v2.6.1.txt
new file mode 100644
index 00000000000..f3d1c30f8e3
--- /dev/null
+++ b/changes-entries/md_v2.6.1.txt
@@ -0,0 +1,13 @@
+ *) 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.
diff --git a/docs/manual/mod/mod_md.xml b/docs/manual/mod/mod_md.xml
index 09bae08a3bb..c383294c0da 100644
--- a/docs/manual/mod/mod_md.xml
+++ b/docs/manual/mod/mod_md.xml
@@ -1393,7 +1393,7 @@ MDMessageCmd /etc/apache/md-message
MDRetryDelay
Time length for first retry, doubled on every consecutive error.
MDRetryDelay duration
- MDRetryDelay 5s
+ MDRetryDelay 30s
server config
@@ -1408,6 +1408,10 @@ MDMessageCmd /etc/apache/md-message
It is kept separate for each certificate renewal. Meaning an error
on one MDomain does not delay the renewals of other domains.
+
+ In mod_md v2.6.1, the default delay has been increased from 5
+ seconds to 30.
+
@@ -1594,4 +1598,26 @@ MDMessageCmd /etc/apache/md-message
+
+
+ MDRenewViaARI
+ usage of the ACME ARI extension (rfc9773).
+ MDRenewViaARI on|off
+ MDRenewViaARI on
+
+ server config
+
+
+
+ En-/Disable certificate renewals triggered via the ACME ARI
+ extension (rfc9773). These renewals happen *in addition* to
+ the mechanism controlled by MDRenewWindow.
+
+ ACME ARI allows an ACME CA to somewhat shape incoming renewal
+ traffic. More importantly though, it can inform clients of
+ urgent renewals, e.g. when a certificate or part of its chain
+ has been revoked.
+
+
+
diff --git a/modules/md/config2.m4 b/modules/md/config2.m4
index e416736a8c1..b20ab3b45e1 100644
--- a/modules/md/config2.m4
+++ b/modules/md/config2.m4
@@ -156,7 +156,6 @@ md_reg.lo dnl
md_status.lo dnl
md_store.lo dnl
md_store_fs.lo dnl
-md_tailscale.lo dnl
md_time.lo dnl
md_util.lo dnl
mod_md.lo dnl
diff --git a/modules/md/md.h b/modules/md/md.h
index 3f298eaa6f3..fb1a270ac8f 100644
--- a/modules/md/md.h
+++ b/modules/md/md.h
@@ -94,6 +94,7 @@ struct md_t {
const char *ca_eab_hmac; /* optional HMAC for external account binding */
const char *profile; /* optional cert profile to order */
int profile_mandatory; /* if profile, when given, is mandatory */
+ int ari_renewals; /* if ACME ARI (RFC 9773) can trigger renewals */
const char *state_descr; /* description of state of NULL */
@@ -119,6 +120,8 @@ struct md_t {
#define MD_KEY_ACTIVATION_DELAY "activation-delay"
#define MD_KEY_ACTIVITY "activity"
#define MD_KEY_AGREEMENT "agreement"
+#define MD_KEY_ARI_CERT_ID "ari-cert-id"
+#define MD_KEY_ARI_RENEWALS "ari-renewals"
#define MD_KEY_AUTHORIZATIONS "authorizations"
#define MD_KEY_BITS "bits"
#define MD_KEY_CA "ca"
diff --git a/modules/md/md_acme.c b/modules/md/md_acme.c
index f8624513ba8..33a7afa310a 100644
--- a/modules/md/md_acme.c
+++ b/modules/md/md_acme.c
@@ -332,7 +332,7 @@ static apr_status_t acmev2_GET_as_POST_init(md_acme_req_t *req, void *baton)
return md_acme_req_body_init(req, NULL);
}
-static apr_status_t md_acme_req_send(md_acme_req_t *req)
+static apr_status_t md_acme_req_send(md_acme_req_t *req, int get_as_post)
{
apr_status_t rv;
md_acme_t *acme = req->acme;
@@ -352,7 +352,7 @@ static apr_status_t md_acme_req_send(md_acme_req_t *req)
if (APR_SUCCESS != rv) goto leave;
}
- if (!strcmp("GET", req->method) && !req->on_init && !req->req_json) {
+ if (get_as_post && !strcmp("GET", req->method) && !req->on_init && !req->req_json) {
/* See
* and
* and
@@ -420,7 +420,7 @@ static apr_status_t md_acme_req_send(md_acme_req_t *req)
if (APR_EAGAIN == rv && req->max_retries > 0) {
--req->max_retries;
- rv = md_acme_req_send(req);
+ rv = md_acme_req_send(req, 1);
}
req = NULL;
@@ -449,14 +449,15 @@ apr_status_t md_acme_POST(md_acme_t *acme, const char *url,
req->on_err = on_err;
req->baton = baton;
- return md_acme_req_send(req);
+ return md_acme_req_send(req, 1);
}
apr_status_t md_acme_GET(md_acme_t *acme, const char *url,
md_acme_req_init_cb *on_init,
md_acme_req_json_cb *on_json,
md_acme_req_res_cb *on_res,
- md_acme_req_err_cb *on_err,
+ md_acme_req_err_cb *on_err,
+ int get_as_post,
void *baton)
{
md_acme_req_t *req;
@@ -472,7 +473,7 @@ apr_status_t md_acme_GET(md_acme_t *acme, const char *url,
req->on_err = on_err;
req->baton = baton;
- return md_acme_req_send(req);
+ return md_acme_req_send(req, get_as_post);
}
void md_acme_report_result(md_acme_t *acme, apr_status_t rv, struct md_result_t *result)
@@ -507,7 +508,7 @@ static apr_status_t on_got_json(md_acme_t *acme, apr_pool_t *p, const apr_table_
}
apr_status_t md_acme_get_json(struct md_json_t **pjson, md_acme_t *acme,
- const char *url, apr_pool_t *p)
+ const char *url, int get_as_post, apr_pool_t *p)
{
apr_status_t rv;
json_ctx ctx;
@@ -515,7 +516,7 @@ apr_status_t md_acme_get_json(struct md_json_t **pjson, md_acme_t *acme,
ctx.pool = p;
ctx.json = NULL;
- rv = md_acme_GET(acme, url, NULL, on_got_json, NULL, NULL, &ctx);
+ rv = md_acme_GET(acme, url, NULL, on_got_json, NULL, NULL, get_as_post, &ctx);
*pjson = (APR_SUCCESS == rv)? ctx.json : NULL;
return rv;
}
@@ -720,6 +721,7 @@ static apr_status_t update_directory(const md_http_response_t *res, void *data)
acme->api.v2.revoke_cert = md_json_dups(acme->p, json, "revokeCert", NULL);
acme->api.v2.key_change = md_json_dups(acme->p, json, "keyChange", NULL);
acme->api.v2.new_nonce = md_json_dups(acme->p, json, "newNonce", NULL);
+ acme->api.v2.renewal_info = md_json_dups(acme->p, json, "renewalInfo", NULL);
/* RFC 8555 only requires "directory" and "newNonce" resources.
* mod_md uses "newAccount" and "newOrder" so check for them.
* But mod_md does not use the "revokeCert" or "keyChange"
diff --git a/modules/md/md_acme.h b/modules/md/md_acme.h
index 9931f92493f..c2b98b412d0 100644
--- a/modules/md/md_acme.h
+++ b/modules/md/md_acme.h
@@ -118,6 +118,7 @@ struct md_acme_t {
const char *key_change;
const char *revoke_cert;
const char *new_nonce;
+ const char *renewal_info;
struct apr_array_header_t *profiles;
} v2;
} api;
@@ -275,6 +276,7 @@ apr_status_t md_acme_GET(md_acme_t *acme, const char *url,
md_acme_req_json_cb *on_json,
md_acme_req_res_cb *on_res,
md_acme_req_err_cb *on_err,
+ int get_as_post,
void *baton);
/**
* 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,
* Retrieve a JSON resource from the ACME server
*/
apr_status_t md_acme_get_json(struct md_json_t **pjson, md_acme_t *acme,
- const char *url, apr_pool_t *p);
+ const char *url, int get_as_post, apr_pool_t *p);
apr_status_t md_acme_req_body_init(md_acme_req_t *req, struct md_json_t *jpayload);
diff --git a/modules/md/md_acme_authz.c b/modules/md/md_acme_authz.c
index fc46274fffd..c77dcdbaeb3 100644
--- a/modules/md/md_acme_authz.c
+++ b/modules/md/md_acme_authz.c
@@ -131,7 +131,7 @@ apr_status_t md_acme_authz_update(md_acme_authz_t *authz, md_acme_t *acme, apr_p
err = "unable to parse response";
log_level = MD_LOG_ERR;
- if (APR_SUCCESS == (rv = md_acme_get_json(&json, acme, authz->url, p))
+ if (APR_SUCCESS == (rv = md_acme_get_json(&json, acme, authz->url, 1, p))
&& (s = md_json_gets(json, MD_KEY_STATUS, NULL))) {
authz->domain = md_json_gets(json, MD_KEY_IDENTIFIER, MD_KEY_VALUE, NULL);
diff --git a/modules/md/md_acme_drive.c b/modules/md/md_acme_drive.c
index f5cd08c5214..94bcc8aab47 100644
--- a/modules/md/md_acme_drive.c
+++ b/modules/md/md_acme_drive.c
@@ -256,7 +256,7 @@ static apr_status_t get_cert(void *baton, int attempt)
(void)attempt;
md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, d->p, "retrieving cert from %s",
ad->order->certificate);
- return md_acme_GET(ad->acme, ad->order->certificate, NULL, NULL, on_add_cert, NULL, d);
+ return md_acme_GET(ad->acme, ad->order->certificate, NULL, NULL, on_add_cert, NULL, 1, d);
}
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)
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
"next chain cert at %s", ad->chain_up_link);
- rv = md_acme_GET(ad->acme, ad->chain_up_link, NULL, NULL, on_add_chain, NULL, d);
+ rv = md_acme_GET(ad->acme, ad->chain_up_link, NULL, NULL, on_add_chain, NULL, 1, d);
if (APR_SUCCESS == rv && nelts == ad->cred->chain->nelts) {
break;
@@ -1094,10 +1094,155 @@ static apr_status_t acme_complete_md(md_t *md, apr_pool_t *p)
return APR_SUCCESS;
}
+static apr_status_t acme_get_ari(md_proto_driver_t *d,
+ struct md_result_t *result,
+ apr_time_t *prenew_at,
+ const char **purl)
+{
+ md_acme_driver_t *ad = d->baton;
+ apr_status_t rv = APR_SUCCESS;
+ const char *ca_effective = NULL;
+ apr_array_header_t *certs;
+ const md_cert_t *cert;
+ int i;
+
+ *prenew_at = 0;
+ *purl = NULL;
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
+ "get ARI status for %s", d->md->name);
+
+ if (d->md->cert_files && d->md->cert_files->nelts) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
+ "%s is configured with static files, no ARI", d->md->name);
+ goto out;
+ }
+
+ if (!d->md->ca_urls || d->md->ca_urls->nelts <= 0) {
+ /* No CA defined? This is checked in several other places, but lets be sure */
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
+ "%s is missing MDCertificateAuthority", d->md->name);
+ goto out;
+ }
+
+ /* always pick the first CA for this */
+ ca_effective = APR_ARRAY_IDX(d->md->ca_urls, 0, const char*);
+
+ certs = apr_array_make(d->p, 5, sizeof(md_cert_t*));
+ for (i = 0; i < md_pkeys_spec_count(d->md->pks); ++i) {
+ const md_pubcert_t *pubcert;
+ if (APR_SUCCESS == md_reg_get_pubcert(&pubcert, d->reg, d->md, i, d->p)) {
+ cert = APR_ARRAY_IDX(pubcert->certs, 0, const md_cert_t*);
+ APR_ARRAY_PUSH(certs, const md_cert_t*) = cert;
+ }
+ }
+
+ if (!certs->nelts) {
+ rv = APR_SUCCESS;
+ goto out;
+ }
+
+ if (APR_SUCCESS != (rv = md_acme_create(&ad->acme, d->p, ca_effective,
+ d->proxy_url, d->ca_file))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p,
+ "create ACME communications");
+ goto out;
+ }
+ if (APR_SUCCESS != (rv = md_acme_setup(ad->acme, result))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p,
+ "setup ACME communications");
+ goto out;
+ }
+ if (ad->acme->version != MD_ACME_VERSION_2 || !ad->acme->api.v2.renewal_info) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
+ "ARI not supported by ACME CA %s", ca_effective);
+ goto out;
+ }
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
+ "assessing ARI status for %d certificates", certs->nelts);
+ for (i = 0; i < certs->nelts; ++i) {
+ const char *ari_cert_id;
+ const char *ari_url = NULL, *ari_expl_url;
+ const char *renew_start, *renew_end;
+ apr_time_t start, end;
+ md_json_t *json;
+ unsigned char c;
+
+ cert = APR_ARRAY_IDX(certs, i, md_cert_t*);
+ if (md_cert_get_ari_cert_id(&ari_cert_id, cert, d->p) != APR_SUCCESS)
+ continue;
+
+ ari_url = apr_psprintf(d->p, "%s/%s", ad->acme->api.v2.renewal_info,
+ ari_cert_id);
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
+ "GET #%d ARI from %s", i, ari_url);
+ if ((rv = md_acme_get_json(&json, ad->acme, ari_url, 0, d->p)) != APR_SUCCESS) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, d->p,
+ "error retrieving ARI from %s", ari_url);
+ continue;
+ }
+
+ if(!json) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, d->p,
+ "ARI returned no JSON from %s", ari_url);
+ continue;
+ }
+
+ renew_start = md_json_gets(json, "suggestedWindow", "start", NULL);
+ renew_end = md_json_gets(json, "suggestedWindow", "end", NULL);
+ ari_expl_url = md_json_gets(json, "explanationURL", NULL);
+ if (!renew_start || !renew_end) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, d->p,
+ "renewal info from CA incomplete for %s", ari_url);
+ continue;
+ }
+ start = md_time_parse_rfc3339(renew_start);
+ end = md_time_parse_rfc3339(renew_end);
+ if (!start) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, d->p,
+ "error parsing CA renew start time: '%s'",
+ renew_start);
+ continue;
+ }
+ if (!end) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, d->p,
+ "error parsing CA renew end time: '%s'",
+ renew_end);
+ continue;
+ }
+ if (start > end) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, d->p,
+ "CA advises weird renewal between '%s' and '%s'",
+ renew_start, renew_end);
+ continue;
+ }
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
+ "CA advises renew via ARI between %s and %s"
+ " (explantion: %s)",
+ renew_start, renew_end,
+ ari_expl_url? ari_expl_url : "none given");
+ /* select a random value between start and end */
+ md_rand_bytes(&c, sizeof(c), d->p);
+ start += apr_time_from_sec((apr_time_sec(end - start) * (c - 128)) / 256);
+ if (!*prenew_at || (start < *prenew_at)) {
+ *prenew_at = start;
+ *purl = apr_pstrdup(d->p, ari_expl_url);
+ }
+ }
+
+ rv = APR_SUCCESS;
+out:
+ return rv;
+}
+
static md_proto_t ACME_PROTO = {
- MD_PROTO_ACME, acme_driver_init, acme_driver_renew,
- acme_driver_preload_init, acme_driver_preload,
+ MD_PROTO_ACME,
+ acme_driver_init,
+ acme_driver_renew,
+ acme_driver_preload_init,
+ acme_driver_preload,
acme_complete_md,
+ acme_get_ari,
};
apr_status_t md_acme_protos_add(apr_hash_t *protos, apr_pool_t *p)
diff --git a/modules/md/md_acme_order.c b/modules/md/md_acme_order.c
index 0a0ad7ff0ae..22d84a2594a 100644
--- a/modules/md/md_acme_order.c
+++ b/modules/md/md_acme_order.c
@@ -264,13 +264,15 @@ typedef struct {
md_acme_t *acme;
const char *name;
const char *profile;
+ const char *ari_cert_id;
apr_array_header_t *domains;
md_result_t *result;
} order_ctx_t;
-#define ORDER_CTX_INIT(ctx, p, o, a, n, d, pf, r) \
+#define ORDER_CTX_INIT(ctx, p, o, a, n, d, pf, cid, r) \
(ctx)->p = (p); (ctx)->order = (o); (ctx)->acme = (a); \
- (ctx)->name = (n); (ctx)->domains = d; (ctx)->profile = pf; (ctx)->result = r
+ (ctx)->name = (n); (ctx)->domains = d; (ctx)->profile = pf; \
+ (ctx)->ari_cert_id = cid;(ctx)->result = r
static apr_status_t identifier_to_json(void *value, md_json_t *json, apr_pool_t *p, void *baton)
{
@@ -291,7 +293,9 @@ static apr_status_t on_init_order_register(md_acme_req_t *req, void *baton)
jpayload = md_json_create(req->p);
md_json_seta(ctx->domains, identifier_to_json, NULL, jpayload, "identifiers", NULL);
if (ctx->profile)
- md_json_sets(ctx->profile, jpayload, "profile", NULL);
+ md_json_sets(ctx->profile, jpayload, "profile", NULL);
+ if (ctx->ari_cert_id)
+ md_json_sets(ctx->ari_cert_id, jpayload, "replaces", NULL);
return md_acme_req_body_init(req, jpayload);
}
@@ -325,13 +329,13 @@ out:
apr_status_t md_acme_order_register(md_acme_order_t **porder, md_acme_t *acme, apr_pool_t *p,
const char *name, apr_array_header_t *domains,
- const char *profile)
+ const char *profile, const char *ari_cert_id)
{
order_ctx_t ctx;
apr_status_t rv;
assert(MD_ACME_VERSION_MAJOR(acme->version) > 1);
- ORDER_CTX_INIT(&ctx, p, NULL, acme, name, domains, profile, NULL);
+ ORDER_CTX_INIT(&ctx, p, NULL, acme, name, domains, profile, ari_cert_id, NULL);
rv = md_acme_POST(acme, acme->api.v2.new_order, on_init_order_register, on_order_upd, NULL, NULL, &ctx);
*porder = (APR_SUCCESS == rv)? ctx.order : NULL;
return rv;
@@ -344,8 +348,8 @@ apr_status_t md_acme_order_update(md_acme_order_t *order, md_acme_t *acme,
apr_status_t rv;
assert(MD_ACME_VERSION_MAJOR(acme->version) > 1);
- ORDER_CTX_INIT(&ctx, p, order, acme, NULL, NULL, NULL, result);
- rv = md_acme_GET(acme, order->url, NULL, on_order_upd, NULL, NULL, &ctx);
+ ORDER_CTX_INIT(&ctx, p, order, acme, NULL, NULL, NULL, NULL, result);
+ rv = md_acme_GET(acme, order->url, NULL, on_order_upd, NULL, NULL, 1, &ctx);
if (APR_SUCCESS != rv && APR_SUCCESS != acme->last->status) {
md_result_dup(result, acme->last);
}
@@ -384,7 +388,7 @@ apr_status_t md_acme_order_await_ready(md_acme_order_t *order, md_acme_t *acme,
apr_status_t rv;
assert(MD_ACME_VERSION_MAJOR(acme->version) > 1);
- ORDER_CTX_INIT(&ctx, p, order, acme, md->name, NULL, NULL, result);
+ ORDER_CTX_INIT(&ctx, p, order, acme, md->name, NULL, NULL, NULL, result);
md_result_activity_setn(result, "Waiting for order to become ready");
rv = md_util_try(await_ready, &ctx, 0, timeout, 0, 0, 1);
@@ -427,7 +431,7 @@ apr_status_t md_acme_order_await_valid(md_acme_order_t *order, md_acme_t *acme,
apr_status_t rv;
assert(MD_ACME_VERSION_MAJOR(acme->version) > 1);
- ORDER_CTX_INIT(&ctx, p, order, acme, md->name, NULL, NULL, result);
+ ORDER_CTX_INIT(&ctx, p, order, acme, md->name, NULL, NULL, NULL, result);
md_result_activity_setn(result, "Waiting for finalized order to become valid");
rv = md_util_try(await_valid, &ctx, 0, timeout, 0, 0, 1);
@@ -556,7 +560,7 @@ apr_status_t md_acme_order_monitor_authzs(md_acme_order_t *order, md_acme_t *acm
order_ctx_t ctx;
apr_status_t rv;
- ORDER_CTX_INIT(&ctx, p, order, acme, md->name, NULL, NULL, result);
+ ORDER_CTX_INIT(&ctx, p, order, acme, md->name, NULL, NULL, NULL, result);
md_result_activity_printf(result, "Monitoring challenge status for %s", md->name);
rv = md_util_try(check_challenges, &ctx, 0, timeout, 0, 0, 1);
diff --git a/modules/md/md_acme_order.h b/modules/md/md_acme_order.h
index 01d73d41b9f..2d08cd9861a 100644
--- a/modules/md/md_acme_order.h
+++ b/modules/md/md_acme_order.h
@@ -77,7 +77,7 @@ apr_status_t md_acme_order_monitor_authzs(md_acme_order_t *order, md_acme_t *acm
apr_status_t md_acme_order_register(md_acme_order_t **porder, md_acme_t *acme, apr_pool_t *p,
const char *name, struct apr_array_header_t *domains,
- const char *profile);
+ const char *profile, const char *ari_cert_id);
apr_status_t md_acme_order_update(md_acme_order_t *order, md_acme_t *acme,
struct md_result_t *result, apr_pool_t *p);
diff --git a/modules/md/md_acmev2_drive.c b/modules/md/md_acmev2_drive.c
index e5821e560ae..de58c7247ca 100644
--- a/modules/md/md_acmev2_drive.c
+++ b/modules/md/md_acmev2_drive.c
@@ -57,7 +57,8 @@ static apr_status_t ad_setup_order(md_proto_driver_t *d, md_result_t *result, in
apr_status_t rv;
md_t *md = ad->md;
const char *profile = NULL;
-
+ const char *ari_cert_id = NULL;
+
assert(ad->md);
assert(ad->acme);
@@ -77,9 +78,34 @@ static apr_status_t ad_setup_order(md_proto_driver_t *d, md_result_t *result, in
md_acme_order_purge(d->store, d->p, MD_SG_STAGING, md, d->env);
}
- md_result_activity_setn(result, "Creating new order");
+ if (ad->cred->spec && ad->md->ca_account) {
+ /* are we replacing a previous certificate on the same account? */
+ int i;
+ for (i = 0; i < md_pkeys_spec_count(d->md->pks); ++i) {
+ md_pkey_spec_t *spec = md_pkeys_spec_get(d->md->pks, i);
+ const md_pubcert_t *pubcert;
+ const md_cert_t *cert;
+ if (md_pkey_spec_eq(ad->cred->spec, spec)) {
+ rv = md_reg_get_pubcert(&pubcert, d->reg, d->md, i, d->p);
+ if (rv == APR_SUCCESS) {
+ cert = APR_ARRAY_IDX(pubcert->certs, 0, const md_cert_t*);
+ if (cert) {
+ md_cert_get_ari_cert_id(&ari_cert_id, cert, d->p);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ md_result_activity_printf(result, "Creating new order, key-spec=%s, "
+ "profile=%s, replacing-cert=%s",
+ ad->cred->spec? md_pkey_spec_to_str(ad->cred->spec, d->p) : "default",
+ ad->profile? ad->profile : "none",
+ ari_cert_id? ari_cert_id : "none");
+
if (ad->profile) {
- if(ad->acme->api.v2.profiles) {
+ if (ad->acme->api.v2.profiles) {
int i;
for (i = 0; !profile && i < ad->acme->api.v2.profiles->nelts; ++i) {
const char *s = APR_ARRAY_IDX(ad->acme->api.v2.profiles, i, const char*);
@@ -104,7 +130,8 @@ static apr_status_t ad_setup_order(md_proto_driver_t *d, md_result_t *result, in
}
}
- rv = md_acme_order_register(&ad->order, ad->acme, d->p, d->md->name, ad->domains, profile);
+ rv = md_acme_order_register(&ad->order, ad->acme, d->p, d->md->name,
+ ad->domains, profile, ari_cert_id);
if (APR_SUCCESS !=rv) goto leave;
rv = md_acme_order_save(d->store, d->p, MD_SG_STAGING, d->md->name, ad->order, 0);
if (APR_SUCCESS != rv) {
diff --git a/modules/md/md_core.c b/modules/md/md_core.c
index 70f20c4bebe..95b289d2d48 100644
--- a/modules/md/md_core.c
+++ b/modules/md/md_core.c
@@ -112,6 +112,8 @@ md_t *md_create_empty(apr_pool_t *p)
md->transitive = -1;
md->acme_tls_1_domains = apr_array_make(p, 5, sizeof(const char *));
md->stapling = -1;
+ md->profile_mandatory = -1;
+ md->ari_renewals = -1;
md->defn_name = "unknown";
md->defn_line_number = 0;
}
@@ -319,6 +321,7 @@ md_json_t *md_to_json(const md_t *md, apr_pool_t *p)
}
if (md->profile) md_json_sets(md->profile, json, MD_KEY_PROFILE, NULL);
md_json_setb(md->profile_mandatory > 0, json, MD_KEY_PROFILE_MANDATORY, NULL);
+ md_json_setb(md->ari_renewals > 0, json, MD_KEY_ARI_RENEWALS, NULL);
return json;
}
return NULL;
@@ -389,6 +392,7 @@ md_t *md_from_json(md_json_t *json, apr_pool_t *p)
md->profile_mandatory = (int)md_json_getb(json, MD_KEY_PROFILE_MANDATORY, NULL);
if (md_json_has_key(json, MD_KEY_PROFILE, NULL))
md->profile = md_json_dups(p, json, MD_KEY_PROFILE, NULL);
+ md->ari_renewals = (int)md_json_getb(json, MD_KEY_ARI_RENEWALS, NULL);
return md;
}
return NULL;
diff --git a/modules/md/md_crypt.c b/modules/md/md_crypt.c
index e56a2c0c9b7..c5a6dd6f9eb 100644
--- a/modules/md/md_crypt.c
+++ b/modules/md/md_crypt.c
@@ -353,6 +353,24 @@ void md_pkeys_spec_add_ec(md_pkeys_spec_t *pks, const char *curve)
md_pkeys_spec_add(pks, spec);
}
+const char *md_pkey_spec_to_str(const md_pkey_spec_t *spec, apr_pool_t *p)
+{
+ switch (spec->type) {
+ case MD_PKEY_TYPE_DEFAULT:
+ return "default";
+ case MD_PKEY_TYPE_RSA:
+ if (spec->params.rsa.bits >= MD_PKEY_RSA_BITS_MIN)
+ return apr_psprintf(p, "rsa-%d", spec->params.rsa.bits);
+ return "rsa";
+ case MD_PKEY_TYPE_EC:
+ if (spec->params.ec.curve)
+ return apr_psprintf(p, "ec-%s", spec->params.ec.curve);
+ return "ec";
+ default:
+ return "unsupported";
+ }
+}
+
md_json_t *md_pkey_spec_to_json(const md_pkey_spec_t *spec, apr_pool_t *p)
{
md_json_t *json = md_json_create(p);
@@ -460,7 +478,7 @@ md_pkeys_spec_t *md_pkeys_spec_from_json(struct md_json_t *json, apr_pool_t *p)
return pks;
}
-static int pkey_spec_eq(md_pkey_spec_t *s1, md_pkey_spec_t *s2)
+int md_pkey_spec_eq(const md_pkey_spec_t *s1, const md_pkey_spec_t *s2)
{
if (s1 == s2) {
return 1;
@@ -496,8 +514,8 @@ int md_pkeys_spec_eq(md_pkeys_spec_t *pks1, md_pkeys_spec_t *pks2)
}
if (pks1 && pks2 && pks1->specs->nelts == pks2->specs->nelts) {
for(i = 0; i < pks1->specs->nelts; ++i) {
- if (!pkey_spec_eq(APR_ARRAY_IDX(pks1->specs, i, md_pkey_spec_t *),
- APR_ARRAY_IDX(pks2->specs, i, md_pkey_spec_t *))) {
+ if (!md_pkey_spec_eq(APR_ARRAY_IDX(pks1->specs, i, md_pkey_spec_t *),
+ APR_ARRAY_IDX(pks2->specs, i, md_pkey_spec_t *))) {
return 0;
}
}
@@ -1302,7 +1320,7 @@ int md_cert_covers_md(md_cert_t *cert, const md_t *md)
const char *md_cert_get_issuer_name(const md_cert_t *cert, apr_pool_t *p)
{
X509_NAME *xname = X509_get_issuer_name(cert->x509);
- if(xname) {
+ if (xname) {
char *name, *s = X509_NAME_oneline(xname, NULL, 0);
name = apr_pstrdup(p, s);
OPENSSL_free(s);
@@ -2193,3 +2211,61 @@ apr_status_t md_check_cert_and_pkey(struct apr_array_header_t *certs, md_pkey_t
return APR_SUCCESS;
}
+
+apr_status_t md_cert_get_ari_cert_id(const char **pari_cert_id,
+ const md_cert_t *cert, apr_pool_t *p)
+{
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ md_data_t akid_buf, ser_buf;
+ AUTHORITY_KEYID *s_aki;
+ const ASN1_INTEGER *aki;
+ const ASN1_INTEGER *serial;
+ BIGNUM *bn;
+ int i = -1, sder_len;
+ unsigned char *ucp, sbuf[256];
+
+ *pari_cert_id = NULL;
+ s_aki = X509_get_ext_d2i(cert->x509, NID_authority_key_identifier, &i, NULL);
+ if (s_aki == NULL) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
+ "cert has no authority key id extension");
+ return APR_ENOENT;
+ }
+ aki = s_aki->keyid;
+ if (aki == NULL) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
+ "cert has no authority key id in extension");
+ return APR_ENOENT;
+ }
+ akid_buf.len = (apr_size_t)ASN1_STRING_length(aki);
+ akid_buf.data = (const char *)ASN1_STRING_get0_data(aki);
+ akid_buf.free_data = NULL;
+
+ serial = X509_get0_serialNumber(cert->x509);
+ if (!serial) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
+ "cert has no serial number");
+ return APR_ENOENT;
+ }
+ memset(&ser_buf, 0, sizeof(ser_buf));
+ bn = ASN1_INTEGER_to_BN(serial, NULL);
+ sder_len = BN_bn2bin(bn, sbuf);
+ OPENSSL_free((void*)bn);
+ if (sder_len < 1)
+ return APR_EINVAL;
+ ser_buf.len = (apr_size_t)sder_len;
+ ser_buf.data = (const char *)sbuf;
+ (void)ucp;
+
+ *pari_cert_id = apr_psprintf(p, "%s.%s",
+ md_util_base64url_encode(&akid_buf, p),
+ md_util_base64url_encode(&ser_buf, p));
+ return APR_SUCCESS;
+#else
+ *pari_cert_id = NULL;
+ (void)cert;
+ (void)p;
+ return APR_ENOTIMPL;
+#endif
+}
+
diff --git a/modules/md/md_crypt.h b/modules/md/md_crypt.h
index e6b3ac2e783..6880cd2c095 100644
--- a/modules/md/md_crypt.h
+++ b/modules/md/md_crypt.h
@@ -68,6 +68,8 @@ typedef struct md_pkey_spec_t {
} params;
} md_pkey_spec_t;
+int md_pkey_spec_eq(const md_pkey_spec_t *s1, const md_pkey_spec_t *s2);
+
typedef struct md_pkeys_spec_t {
apr_pool_t *p;
struct apr_array_header_t *specs;
@@ -90,6 +92,8 @@ md_pkey_spec_t *md_pkeys_spec_get(const md_pkeys_spec_t *pks, int index);
int md_pkeys_spec_count(const md_pkeys_spec_t *pks);
void md_pkeys_spec_add(md_pkeys_spec_t *pks, md_pkey_spec_t *spec);
+const char *md_pkey_spec_to_str(const md_pkey_spec_t *spec, apr_pool_t *p);
+
struct md_json_t *md_pkey_spec_to_json(const md_pkey_spec_t *spec, apr_pool_t *p);
md_pkey_spec_t *md_pkey_spec_from_json(struct md_json_t *json, apr_pool_t *p);
struct md_json_t *md_pkeys_spec_to_json(const md_pkeys_spec_t *pks, apr_pool_t *p);
@@ -235,6 +239,9 @@ apr_status_t md_cert_get_ocsp_responder_url(const char **purl, apr_pool_t *p, co
apr_status_t md_check_cert_and_pkey(struct apr_array_header_t *certs, md_pkey_t *pkey);
+apr_status_t md_cert_get_ari_cert_id(const char **pari_cert_id,
+ const md_cert_t *cert, apr_pool_t *p);
+
/**************************************************************************************************/
/* X509 certificate transparency */
diff --git a/modules/md/md_http.c b/modules/md/md_http.c
index 0d21e7b14c6..283f4be1352 100644
--- a/modules/md/md_http.c
+++ b/modules/md/md_http.c
@@ -242,11 +242,13 @@ static apr_status_t req_create(md_http_request_t **preq, md_http_t *http,
void md_http_req_destroy(md_http_request_t *req)
{
- if (req->internals) {
- req->http->impl->req_cleanup(req);
- req->internals = NULL;
+ if (req) {
+ if (req->internals) {
+ req->http->impl->req_cleanup(req);
+ req->internals = NULL;
+ }
+ apr_pool_destroy(req->pool);
}
- apr_pool_destroy(req->pool);
}
void md_http_set_on_status_cb(md_http_request_t *req, md_http_status_cb *cb, void *baton)
@@ -341,7 +343,7 @@ cleanup:
}
else {
*preq = NULL;
- if (req) md_http_req_destroy(req);
+ md_http_req_destroy(req);
}
return rv;
}
diff --git a/modules/md/md_json.c b/modules/md/md_json.c
index e0f977ea564..bd0e1c5a3b7 100644
--- a/modules/md/md_json.c
+++ b/modules/md/md_json.c
@@ -1186,14 +1186,18 @@ apr_status_t md_json_read_http(md_json_t **pjson, apr_pool_t *pool, const md_htt
{
apr_status_t rv = APR_ENOENT;
const char *ctype, *p;
+ apr_size_t ctype_len;
*pjson = NULL;
if (!res->body) goto cleanup;
ctype = md_util_parse_ct(res->req->pool, apr_table_get(res->headers, "content-type"));
if (!ctype) goto cleanup;
- p = ctype + strlen(ctype) +1;
- if (!strcmp(p - sizeof("/json"), "/json")
- || !strcmp(p - sizeof("+json"), "+json")) {
+ ctype_len = strlen(ctype);
+ if (ctype_len < sizeof("/json")) goto cleanup;
+ p = ctype + ctype_len + 1; /* point to the terminating 0 */
+ if (!strcmp(p - sizeof("/json"), "/json") ||
+ !strcmp(p - sizeof("+json"), "+json") ||
+ !strcmp(ctype, "text/plain")) {
rv = md_json_readb(pjson, pool, res->body);
}
cleanup:
diff --git a/modules/md/md_reg.c b/modules/md/md_reg.c
index d0a41de177d..5da2f6dd4bc 100644
--- a/modules/md/md_reg.c
+++ b/modules/md/md_reg.c
@@ -34,7 +34,6 @@
#include "md_ocsp.h"
#include "md_store.h"
#include "md_status.h"
-#include "md_tailscale.h"
#include "md_util.h"
#include "md_acme.h"
@@ -122,8 +121,7 @@ apr_status_t md_reg_create(md_reg_t **preg, apr_pool_t *p, struct md_store_t *st
md_timeslice_create(®->renew_window, p, MD_TIME_LIFE_NORM, MD_TIME_RENEW_WINDOW_DEF);
md_timeslice_create(®->warn_window, p, MD_TIME_LIFE_NORM, MD_TIME_WARN_WINDOW_DEF);
- if (APR_SUCCESS == (rv = md_acme_protos_add(reg->protos, p))
- && APR_SUCCESS == (rv = md_tailscale_protos_add(reg->protos, p))) {
+ if (APR_SUCCESS == (rv = md_acme_protos_add(reg->protos, p))) {
rv = load_props(reg, p);
}
@@ -239,9 +237,9 @@ static apr_status_t state_init(md_reg_t *reg, apr_pool_t *p, md_t *md)
if (md->renew_window == NULL) md->renew_window = reg->renew_window;
if (md->warn_window == NULL) md->warn_window = reg->warn_window;
- if(is_static) {
- if(md->renew_mode == MD_RENEW_AUTO)
- md->renew_mode = MD_RENEW_MANUAL;
+ if (is_static) {
+ if (md->renew_mode == MD_RENEW_AUTO)
+ md->renew_mode = MD_RENEW_MANUAL;
}
if (md->domains && md->domains->pool != p) {
@@ -691,13 +689,18 @@ apr_time_t md_reg_renew_at(md_reg_t *reg, const md_t *md, apr_pool_t *p)
const md_cert_t *cert;
md_timeperiod_t certlife, renewal;
int i;
- apr_time_t renew_at = 0;
+ apr_time_t renew_at = 0, now = apr_time_now();
apr_status_t rv;
- if (md->state == MD_S_INCOMPLETE) return apr_time_now();
+ if (md->state == MD_S_INCOMPLETE) return now;
for (i = 0; i < md_cert_count(md); ++i) {
rv = md_reg_get_pubcert(&pub, reg, md, i, p);
- if (APR_STATUS_IS_ENOENT(rv)) return apr_time_now();
+ if (APR_STATUS_IS_ENOENT(rv)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, p,
+ "md(%s): is missing certificate #%d",
+ md->name, i);
+ return now;
+ }
if (APR_SUCCESS == rv) {
cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*);
certlife.start = md_cert_get_not_before(cert);
@@ -705,7 +708,7 @@ apr_time_t md_reg_renew_at(md_reg_t *reg, const md_t *md, apr_pool_t *p)
renewal = md_timeperiod_slice_before_end(&certlife, md->renew_window);
if (md_log_is_level(p, MD_LOG_TRACE1)) {
- md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, p,
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, p,
"md[%s]: certificate(%d) valid[%s] renewal[%s]",
md->name, i,
md_timeperiod_print(p, &certlife),
@@ -713,7 +716,7 @@ apr_time_t md_reg_renew_at(md_reg_t *reg, const md_t *md, apr_pool_t *p)
}
if (renew_at == 0 || renewal.start < renew_at) {
- renew_at = renewal.start;
+ renew_at = renewal.start;
}
}
}
@@ -1002,6 +1005,8 @@ apr_status_t md_reg_sync_finish(md_reg_t *reg, md_t *md, apr_pool_t *p, apr_pool
&& !MD_VAL_UPDATE(md, old, must_staple)
&& md_array_str_eq(md->acme_tls_1_domains, old->acme_tls_1_domains, 0)
&& !MD_VAL_UPDATE(md, old, stapling)
+ && !MD_VAL_UPDATE(md, old, profile_mandatory)
+ && !MD_VAL_UPDATE(md, old, ari_renewals)
&& md_array_str_eq(md->contacts, old->contacts, 0)
&& md_array_str_eq(md->cert_files, old->cert_files, 0)
&& md_array_str_eq(md->pkey_files, old->pkey_files, 0)
@@ -1201,6 +1206,53 @@ apr_status_t md_reg_renew(md_reg_t *reg, const md_t *md, apr_table_t *env,
return md_util_pool_vdo(run_renew, reg, p, md, env, reset, attempt, result, NULL);
}
+static apr_status_t run_get_ari(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+ md_reg_t *reg = baton;
+ apr_time_t *prenew_at = 0;
+ const char **purl;
+ const md_t *md;
+ md_proto_driver_t *driver;
+ apr_table_t *env;
+ apr_status_t rv;
+ md_result_t *result;
+
+ (void)p;
+ prenew_at = va_arg(ap, apr_time_t *);
+ purl = va_arg(ap, const char **);
+ md = va_arg(ap, const md_t *);
+ env = va_arg(ap, apr_table_t *);
+ result = va_arg(ap, md_result_t *);
+
+ rv = run_init(reg, ptemp, &driver, md, 0, env, result, NULL);
+ if (APR_SUCCESS == rv) {
+ if (driver->proto->get_ari) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp, "%s: run get_ari",
+ md->name);
+ rv = driver->proto->get_ari(driver, result, prenew_at, purl);
+ }
+ else {
+ /* unsupported by protocol */
+ *prenew_at = 0;
+ *purl = NULL;
+ rv = APR_ENOTIMPL;
+ }
+ }
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "%s: get_ari done", md->name);
+ return rv;
+}
+
+apr_time_t md_reg_ari_renew_at(const char **purl, md_reg_t *reg,
+ const md_t *md, struct apr_table_t *env,
+ struct md_result_t *result, apr_pool_t *p)
+{
+ apr_time_t renew_at = 0;
+ *purl = NULL;
+ if (md_util_pool_vdo(run_get_ari, reg, p, &renew_at, purl, md, env, result, NULL) == APR_SUCCESS)
+ return renew_at;
+ return 0;
+}
+
static apr_status_t run_load_staging(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
{
md_reg_t *reg = baton;
@@ -1379,7 +1431,7 @@ int md_reg_has_revoked_certs(md_reg_t *reg, struct md_ocsp_reg_t *ocsp,
if (APR_SUCCESS != md_reg_get_pubcert(&pubcert, reg, md, i, p))
continue;
cert = APR_ARRAY_IDX(pubcert->certs, 0, const md_cert_t*);
- if(!cert)
+ if (!cert)
continue;
rv = md_ocsp_get_meta(&cert_stat, &ocsp_valid, ocsp, cert, p, md);
if (APR_SUCCESS == rv && cert_stat == MD_OCSP_CERT_ST_REVOKED) {
diff --git a/modules/md/md_reg.h b/modules/md/md_reg.h
index 191b026e46a..ce83c255e9b 100644
--- a/modules/md/md_reg.h
+++ b/modules/md/md_reg.h
@@ -183,6 +183,14 @@ int md_reg_should_renew(md_reg_t *reg, const md_t *md, apr_pool_t *p);
*/
apr_time_t md_reg_renew_at(md_reg_t *reg, const md_t *md, apr_pool_t *p);
+/* Check ACME Renewal Info, if available, for the CA recommended time
+ * (and optional reason URL) for certificate renewal.
+ * Returns 0 if not availble.
+ */
+apr_time_t md_reg_ari_renew_at(const char **purl, md_reg_t *reg,
+ const md_t *md, struct apr_table_t *env,
+ struct md_result_t *result, apr_pool_t *p);
+
/**
* Return the timestamp up to which *all* certificates for the MD can be used.
* A value of 0 indicates that there is no certificate.
@@ -233,6 +241,8 @@ typedef apr_status_t md_proto_init_preload_cb(md_proto_driver_t *driver, struct
typedef apr_status_t md_proto_preload_cb(md_proto_driver_t *driver,
md_store_group_t group, struct md_result_t *result);
typedef apr_status_t md_proto_complete_md_cb(md_t *md, apr_pool_t *p);
+typedef apr_status_t md_proto_get_ari(md_proto_driver_t *driver, struct md_result_t *result,
+ apr_time_t *prenew_at, const char **purl);
struct md_proto_t {
const char *protocol;
@@ -241,6 +251,7 @@ struct md_proto_t {
md_proto_init_preload_cb *init_preload;
md_proto_preload_cb *preload;
md_proto_complete_md_cb *complete_md;
+ md_proto_get_ari *get_ari;
};
/**
diff --git a/modules/md/md_status.c b/modules/md/md_status.c
index 5490e770108..e7d764519f7 100644
--- a/modules/md/md_status.c
+++ b/modules/md/md_status.c
@@ -55,8 +55,8 @@ static apr_status_t status_get_cert_json(md_json_t **pjson, const md_cert_t *cer
if (issuer_name)
md_json_sets(issuer_name, json, MD_KEY_ISSUER_NAME, NULL);
rv = md_cert_get_issuers_uri(&issuer_uri, cert, p);
- if(rv == APR_SUCCESS && issuer_uri)
- md_json_sets(issuer_uri, json, MD_KEY_ISSUER_URI, NULL);
+ if (rv == APR_SUCCESS && issuer_uri)
+ md_json_sets(issuer_uri, json, MD_KEY_ISSUER_URI, NULL);
valid.start = md_cert_get_not_before(cert);
valid.end = md_cert_get_not_after(cert);
md_json_set_timeperiod(&valid, json, MD_KEY_VALID, NULL);
@@ -120,9 +120,14 @@ static apr_status_t status_get_cert_json_ex(
md_json_t *certj, *jobj;
md_timeperiod_t ocsp_valid;
md_ocsp_cert_stat_t cert_stat;
+ const char *ari_cert_id;
apr_status_t rv;
if (APR_SUCCESS != (rv = status_get_cert_json(&certj, cert, p))) goto leave;
+
+ if (APR_SUCCESS == md_cert_get_ari_cert_id(&ari_cert_id, cert, p))
+ md_json_sets(ari_cert_id, certj, MD_KEY_ARI_CERT_ID, NULL);
+
if (md->stapling && ocsp) {
rv = md_ocsp_get_meta(&cert_stat, &ocsp_valid, ocsp, cert, p, md);
if (APR_SUCCESS == rv) {
@@ -135,6 +140,7 @@ static apr_status_t status_get_cert_json_ex(
md_json_setj(jobj, certj, MD_KEY_OCSP, MD_KEY_RENEWAL, NULL);
}
}
+
leave:
*pjson = (APR_SUCCESS == rv)? certj : NULL;
return rv;
@@ -215,7 +221,7 @@ static apr_status_t status_get_md_json(md_json_t **pjson, const md_t *md,
md_reg_t *reg, md_ocsp_reg_t *ocsp,
int with_logs, apr_pool_t *p)
{
- md_json_t *mdj, *certsj, *jobj;
+ md_json_t *mdj, *certsj, *jobj = NULL;
int renew;
const md_pubcert_t *pubcert;
const md_cert_t *cert = NULL;
@@ -245,20 +251,28 @@ static apr_status_t status_get_md_json(md_json_t **pjson, const md_t *md,
md_json_setb(md->stapling, mdj, MD_KEY_STAPLING, NULL);
md_json_setb(md->watched, mdj, MD_KEY_WATCHED, NULL);
- renew = md_reg_should_renew(reg, md, p);
+
+ renew = FALSE;
+ rv = job_loadj(&jobj, MD_SG_STAGING, md->name, reg, with_logs, p);
+ if (rv == APR_SUCCESS)
+ renew = TRUE;
+ else if (APR_STATUS_IS_ENOENT(rv)) {
+ rv = APR_SUCCESS;
+ renew = md_reg_should_renew(reg, md, p);
+ }
+ else
+ goto leave;
+
if (renew) {
md_json_setb(renew, mdj, MD_KEY_RENEW, NULL);
- rv = job_loadj(&jobj, MD_SG_STAGING, md->name, reg, with_logs, p);
- if (APR_SUCCESS == rv) {
+ if (jobj) {
if (APR_SUCCESS == get_staging_certs_json(&certsj, md, reg, p)) {
md_json_setj(certsj, jobj, MD_KEY_CERT, NULL);
}
md_json_setj(jobj, mdj, MD_KEY_RENEWAL, NULL);
}
- else if (APR_STATUS_IS_ENOENT(rv)) rv = APR_SUCCESS;
- else goto leave;
}
-
+
leave:
if (APR_SUCCESS != rv) {
md_json_setl(rv, mdj, MD_KEY_ERROR, NULL);
@@ -598,11 +612,19 @@ apr_time_t md_job_delay_on_errors(md_job_t *job, int err_count, const char *last
delay = max_delay;
}
else if (err_count > 0) {
- /* back off duration, depending on the errors we encounter in a row */
- delay = job->min_delay << (err_count - 1);
- if (delay > max_delay) {
- delay = max_delay;
+ /* back off duration, depending on the errors we encounter in a row.
+ * As apr_time_t is signed, this might wrap around*/
+ int i;
+ delay = job->min_delay;
+ for (i = 0; i < err_count; ++i) {
+ delay <<= 1;
+ if ((delay <= 0) || (delay > max_delay)) {
+ delay = max_delay;
+ break;
+ }
}
+ if (delay > max_delay)
+ delay = max_delay;
}
if (delay > 0) {
/* jitter the delay by +/- 0-50%.
diff --git a/modules/md/md_tailscale.c b/modules/md/md_tailscale.c
deleted file mode 100644
index c8d2bad64c5..00000000000
--- a/modules/md/md_tailscale.c
+++ /dev/null
@@ -1,383 +0,0 @@
-/* Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include
-#include
-
-#include
-#include
-#include
-#include
-
-#include "md.h"
-#include "md_crypt.h"
-#include "md_json.h"
-#include "md_http.h"
-#include "md_log.h"
-#include "md_result.h"
-#include "md_reg.h"
-#include "md_store.h"
-#include "md_util.h"
-
-#include "md_tailscale.h"
-
-typedef struct {
- apr_pool_t *pool;
- md_proto_driver_t *driver;
- const char *unix_socket_path;
- md_t *md;
- apr_array_header_t *chain;
- md_pkey_t *pkey;
-} ts_ctx_t;
-
-static apr_status_t ts_init(md_proto_driver_t *d, md_result_t *result)
-{
- ts_ctx_t *ts_ctx;
- apr_uri_t uri;
- const char *ca_url;
- apr_status_t rv = APR_SUCCESS;
-
- md_result_set(result, APR_SUCCESS, NULL);
- ts_ctx = apr_pcalloc(d->p, sizeof(*ts_ctx));
- ts_ctx->pool = d->p;
- ts_ctx->driver = d;
- ts_ctx->chain = apr_array_make(d->p, 5, sizeof(md_cert_t *));
-
- ca_url = (d->md->ca_urls && !apr_is_empty_array(d->md->ca_urls))?
- APR_ARRAY_IDX(d->md->ca_urls, 0, const char*) : NULL;
- if (!ca_url) {
- ca_url = MD_TAILSCALE_DEF_URL;
- }
- rv = apr_uri_parse(d->p, ca_url, &uri);
- if (APR_SUCCESS != rv) {
- md_result_printf(result, rv, "error parsing CA URL `%s`", ca_url);
- goto leave;
- }
- if (uri.scheme && uri.scheme[0] && strcmp("file", uri.scheme)) {
- rv = APR_ENOTIMPL;
- md_result_printf(result, rv, "non `file` URLs not supported, CA URL is `%s`",
- ca_url);
- goto leave;
- }
- if (uri.hostname && uri.hostname[0] && strcmp("localhost", uri.hostname)) {
- rv = APR_ENOTIMPL;
- md_result_printf(result, rv, "non `localhost` URLs not supported, CA URL is `%s`",
- ca_url);
- goto leave;
- }
- ts_ctx->unix_socket_path = uri.path;
- d->baton = ts_ctx;
-
-leave:
- return rv;
-}
-
-static apr_status_t ts_preload_init(md_proto_driver_t *d, md_result_t *result)
-{
- return ts_init(d, result);
-}
-
-static apr_status_t ts_preload(md_proto_driver_t *d,
- md_store_group_t load_group, md_result_t *result)
-{
- apr_status_t rv;
- md_t *md;
- md_credentials_t *creds;
- md_pkey_spec_t *pkspec;
- apr_array_header_t *all_creds;
- const char *name;
- int i;
-
- name = d->md->name;
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: preload start", name);
- /* Load data from MD_SG_STAGING and save it into "load_group".
- */
- if (APR_SUCCESS != (rv = md_load(d->store, MD_SG_STAGING, name, &md, d->p))) {
- md_result_set(result, rv, "loading staged md.json");
- goto leave;
- }
-
- /* tailscale generates one cert+key with key specification being whatever
- * it chooses. Use the NULL spec here.
- */
- all_creds = apr_array_make(d->p, 5, sizeof(md_credentials_t*));
- pkspec = NULL;
- if (APR_SUCCESS != (rv = md_creds_load(d->store, MD_SG_STAGING, name, pkspec, &creds, d->p))) {
- md_result_printf(result, rv, "loading staged credentials");
- goto leave;
- }
- if (!creds->chain) {
- rv = APR_ENOENT;
- md_result_printf(result, rv, "no certificate in staged credentials");
- goto leave;
- }
- if (APR_SUCCESS != (rv = md_check_cert_and_pkey(creds->chain, creds->pkey))) {
- md_result_printf(result, rv, "certificate and private key do not match in staged credentials");
- goto leave;
- }
- APR_ARRAY_PUSH(all_creds, md_credentials_t*) = creds;
-
- md_result_activity_setn(result, "purging store tmp space");
- rv = md_store_purge(d->store, d->p, load_group, name);
- if (APR_SUCCESS != rv) {
- md_result_set(result, rv, NULL);
- goto leave;
- }
-
- md_result_activity_setn(result, "saving staged md/privkey/pubcert");
- if (APR_SUCCESS != (rv = md_save(d->store, d->p, load_group, md, 1))) {
- md_result_set(result, rv, "writing md.json");
- goto leave;
- }
-
- for (i = 0; i < all_creds->nelts; ++i) {
- creds = APR_ARRAY_IDX(all_creds, i, md_credentials_t*);
- if (APR_SUCCESS != (rv = md_creds_save(d->store, d->p, load_group, name, creds, 1))) {
- md_result_printf(result, rv, "writing credentials #%d", i);
- goto leave;
- }
- }
-
- md_result_set(result, APR_SUCCESS, "saved staged data successfully");
-
-leave:
- md_result_log(result, MD_LOG_DEBUG);
- return rv;
-}
-
-static apr_status_t rv_of_response(const md_http_response_t *res)
-{
- switch (res->status) {
- case 200:
- return APR_SUCCESS;
- case 400:
- return APR_EINVAL;
- case 401: /* sectigo returns this instead of 403 */
- case 403:
- return APR_EACCES;
- case 404:
- return APR_ENOENT;
- default:
- return APR_EGENERAL;
- }
- return APR_SUCCESS;
-}
-
-static apr_status_t on_get_cert(const md_http_response_t *res, void *baton)
-{
- ts_ctx_t *ts_ctx = baton;
- apr_status_t rv;
-
- rv = rv_of_response(res);
- if (APR_SUCCESS != rv) goto leave;
- apr_array_clear(ts_ctx->chain);
- rv = md_cert_chain_read_http(ts_ctx->chain, ts_ctx->pool, res);
- if (APR_SUCCESS != rv) goto leave;
-
-leave:
- return rv;
-}
-
-static apr_status_t on_get_key(const md_http_response_t *res, void *baton)
-{
- ts_ctx_t *ts_ctx = baton;
- apr_status_t rv;
-
- rv = rv_of_response(res);
- if (APR_SUCCESS != rv) goto leave;
- rv = md_pkey_read_http(&ts_ctx->pkey, ts_ctx->pool, res);
- if (APR_SUCCESS != rv) goto leave;
-
-leave:
- return rv;
-}
-
-static apr_status_t ts_renew(md_proto_driver_t *d, md_result_t *result)
-{
- const char *name, *domain, *url;
- apr_status_t rv = APR_ENOENT;
- ts_ctx_t *ts_ctx = d->baton;
- md_http_t *http;
- const md_pubcert_t *pubcert;
- md_cert_t *old_cert, *new_cert;
- int reset_staging = d->reset;
-
- /* "renewing" the certificate from tailscale. Since tailscale has its
- * own ideas on when to do this, we can only inspect the certificate
- * it gives us and see if it is different from the current one we have.
- * (if we have any. first time, lacking a cert, any it gives us is
- * considered as 'renewed'.)
- */
- name = d->md->name;
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: renewing cert", name);
-
- /* When not explicitly told to reset, we check the existing data. If
- * it is incomplete or old, we trigger the reset for a clean start. */
- if (!reset_staging) {
- md_result_activity_setn(result, "Checking staging area");
- rv = md_load(d->store, MD_SG_STAGING, d->md->name, &ts_ctx->md, d->p);
- if (APR_SUCCESS == rv) {
- /* So, we have a copy in staging, but is it a recent or an old one? */
- if (md_is_newer(d->store, MD_SG_DOMAINS, MD_SG_STAGING, d->md->name, d->p)) {
- reset_staging = 1;
- }
- }
- else if (APR_STATUS_IS_ENOENT(rv)) {
- reset_staging = 1;
- rv = APR_SUCCESS;
- }
- }
-
- if (reset_staging) {
- md_result_activity_setn(result, "Resetting staging area");
- /* reset the staging area for this domain */
- rv = md_store_purge(d->store, d->p, MD_SG_STAGING, d->md->name);
- md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p,
- "%s: reset staging area", d->md->name);
- if (APR_SUCCESS != rv && !APR_STATUS_IS_ENOENT(rv)) {
- md_result_printf(result, rv, "resetting staging area");
- goto leave;
- }
- rv = APR_SUCCESS;
- ts_ctx->md = NULL;
- }
-
- if (!ts_ctx->md || !md_array_str_eq(ts_ctx->md->ca_urls, d->md->ca_urls, 1)) {
- md_result_activity_printf(result, "Resetting staging for %s", d->md->name);
- /* re-initialize staging */
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: setup staging", d->md->name);
- md_store_purge(d->store, d->p, MD_SG_STAGING, d->md->name);
- ts_ctx->md = md_copy(d->p, d->md);
- rv = md_save(d->store, d->p, MD_SG_STAGING, ts_ctx->md, 0);
- if (APR_SUCCESS != rv) {
- md_result_printf(result, rv, "Saving MD information in staging area.");
- md_result_log(result, MD_LOG_ERR);
- goto leave;
- }
- }
-
- if (!ts_ctx->unix_socket_path) {
- rv = APR_ENOTIMPL;
- md_result_set(result, rv, "only unix sockets are supported for tailscale connections");
- goto leave;
- }
-
- rv = md_util_is_unix_socket(ts_ctx->unix_socket_path, d->p);
- if (APR_SUCCESS != rv) {
- md_result_printf(result, rv, "tailscale socket not available, may not be up: %s",
- ts_ctx->unix_socket_path);
- goto leave;
- }
-
- rv = md_http_create(&http, d->p,
- apr_psprintf(d->p, "Apache mod_md/%s", MOD_MD_VERSION),
- NULL);
- if (APR_SUCCESS != rv) {
- md_result_set(result, rv, "creating http context");
- goto leave;
- }
- md_http_set_unix_socket_path(http, ts_ctx->unix_socket_path);
-
- domain = (d->md->domains->nelts > 0)?
- APR_ARRAY_IDX(d->md->domains, 0, const char*) : NULL;
- if (!domain) {
- rv = APR_EINVAL;
- md_result_set(result, rv, "no domain names available");
- }
-
- url = apr_psprintf(d->p, "http://localhost/localapi/v0/cert/%s?type=crt",
- domain);
- rv = md_http_GET_perform(http, url, NULL, on_get_cert, ts_ctx);
- if (APR_SUCCESS != rv) {
- md_result_set(result, rv, "retrieving certificate from tailscale");
- goto leave;
- }
- if (ts_ctx->chain->nelts <= 0) {
- rv = APR_ENOENT;
- md_result_set(result, rv, "tailscale returned no certificates");
- goto leave;
- }
-
- /* Got the key and the chain, is it new? */
- rv = md_reg_get_pubcert(&pubcert, d->reg,d->md, 0, d->p);
- if (APR_SUCCESS == rv) {
- old_cert = APR_ARRAY_IDX(pubcert->certs, 0, md_cert_t*);
- new_cert = APR_ARRAY_IDX(ts_ctx->chain, 0, md_cert_t*);
- if (md_certs_are_equal(old_cert, new_cert)) {
- /* tailscale has not renewed the certificate, yet */
- rv = APR_ENOENT;
- md_result_set(result, rv, "tailscale has not renewed the certificate yet");
- /* let's check this daily */
- md_result_delay_set(result, apr_time_now() + apr_time_from_sec(MD_SECS_PER_DAY));
- goto leave;
- }
- }
-
- /* We have a new certificate (or had none before).
- * Get the key and store both in STAGING.
- */
- url = apr_psprintf(d->p, "http://localhost/localapi/v0/cert/%s?type=key",
- domain);
- rv = md_http_GET_perform(http, url, NULL, on_get_key, ts_ctx);
- if (APR_SUCCESS != rv) {
- md_result_set(result, rv, "retrieving key from tailscale");
- goto leave;
- }
-
- rv = md_pkey_save(d->store, d->p, MD_SG_STAGING, name, NULL, ts_ctx->pkey, 1);
- if (APR_SUCCESS != rv) {
- md_result_set(result, rv, "saving private key");
- goto leave;
- }
-
- rv = md_pubcert_save(d->store, d->p, MD_SG_STAGING, name,
- NULL, ts_ctx->chain, 1);
- if (APR_SUCCESS != rv) {
- md_result_printf(result, rv, "saving new certificate chain.");
- goto leave;
- }
-
- md_result_set(result, APR_SUCCESS,
- "A new tailscale certificate has been retrieved successfully and can "
- "be used. A graceful server restart is recommended.");
-
-leave:
- md_result_log(result, MD_LOG_DEBUG);
- return rv;
-}
-
-static apr_status_t ts_complete_md(md_t *md, apr_pool_t *p)
-{
- (void)p;
- if (!md->ca_urls) {
- md->ca_urls = apr_array_make(p, 3, sizeof(const char *));
- APR_ARRAY_PUSH(md->ca_urls, const char*) = MD_TAILSCALE_DEF_URL;
- }
- return APR_SUCCESS;
-}
-
-
-static md_proto_t TAILSCALE_PROTO = {
- MD_PROTO_TAILSCALE, ts_init, ts_renew,
- ts_preload_init, ts_preload, ts_complete_md,
-};
-
-apr_status_t md_tailscale_protos_add(apr_hash_t *protos, apr_pool_t *p)
-{
- (void)p;
- apr_hash_set(protos, MD_PROTO_TAILSCALE, sizeof(MD_PROTO_TAILSCALE)-1, &TAILSCALE_PROTO);
- return APR_SUCCESS;
-}
diff --git a/modules/md/md_tailscale.h b/modules/md/md_tailscale.h
deleted file mode 100644
index 67a874dc28c..00000000000
--- a/modules/md/md_tailscale.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/* Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef mod_md_md_tailscale_h
-#define mod_md_md_tailscale_h
-
-#define MD_PROTO_TAILSCALE "tailscale"
-
-apr_status_t md_tailscale_protos_add(struct apr_hash_t *protos, apr_pool_t *p);
-
-#endif /* mod_md_md_tailscale_h */
-
diff --git a/modules/md/md_time.c b/modules/md/md_time.c
index 268ca83c1b6..97db6071b1a 100644
--- a/modules/md/md_time.c
+++ b/modules/md/md_time.c
@@ -323,3 +323,70 @@ md_timeperiod_t md_timeperiod_common(const md_timeperiod_t *a, const md_timeperi
}
return c;
}
+
+apr_time_t md_time_parse_rfc3339(const char *s)
+{
+ apr_time_exp_t t;
+ apr_time_t ts;
+ apr_size_t i = 0;
+
+ memset(&t, 0, sizeof(t));
+ if (!apr_isdigit(s[0]) || !apr_isdigit(s[1]) || !apr_isdigit(s[2]) ||
+ !apr_isdigit(s[3]) ||
+ (s[4] != '-') ||
+ !apr_isdigit(s[5]) || !apr_isdigit(s[6]) ||
+ (s[7] != '-') ||
+ !apr_isdigit(s[8]) || !apr_isdigit(s[9]) ||
+ ((s[10] != 'T') && (s[10] != 't')) ||
+ !apr_isdigit(s[11]) || !apr_isdigit(s[12]) ||
+ (s[13] != ':') ||
+ !apr_isdigit(s[14]) || !apr_isdigit(s[15]) ||
+ (s[16] != ':') ||
+ !apr_isdigit(s[17]) || !apr_isdigit(s[18])
+ )
+ return 0;
+ t.tm_year = (s[0] - '0') * 1000;
+ t.tm_year += (s[1] - '0') * 100;
+ t.tm_year += (s[2] - '0') * 10;
+ t.tm_year += (s[3] - '0');
+ t.tm_year -= 1900; /* the apr time 0 point */
+ t.tm_mon = (s[5] - '0') * 10;
+ t.tm_mon += (s[6] - '0') - 1; /* -1 since January is 0 not 1. */
+ t.tm_mday = (s[8] - '0') * 10;
+ t.tm_mday += (s[9] - '0');
+ t.tm_hour = (s[11] - '0') * 10;
+ t.tm_hour += (s[12] - '0');
+ t.tm_min = (s[14] - '0') * 10;
+ t.tm_min += (s[15] - '0');
+ t.tm_sec = (s[17] - '0') * 10;
+ t.tm_sec += (s[18] - '0');
+
+ i = 19;
+ if (s[i] == '.') {
+ for(i += 1; apr_isdigit(s[i]); ++i)
+ ; /* skip for now */
+ }
+
+ if ((s[i] == 'Z') || (s[i] == 'z')) {
+ /* alredy GMT, done */
+ t.tm_gmtoff = 0;
+ }
+ else if (((s[i] == '+') || (s[i] == '-')) &&
+ apr_isdigit(s[i+1]) && apr_isdigit(s[i+2]) &&
+ (s[i+3] == ':') &&
+ apr_isdigit(s[i+4]) && apr_isdigit(s[i+5])) {
+ /* tm_gmtoff is in seconds */
+ t.tm_gmtoff = (s[i+1] - '0') * 10 * (60 * 60);
+ t.tm_gmtoff += (s[i+2] - '0') * (60 * 60);
+ t.tm_gmtoff = (s[i+4] - '0') * 10 * 60;
+ t.tm_gmtoff += (s[i+5] - '0') * 60;
+ if (s[i] == '-')
+ t.tm_gmtoff *= -1;
+ }
+ else
+ return 0;
+
+ if (APR_SUCCESS == apr_time_exp_gmt_get(&ts, &t))
+ return ts;
+ return 0;
+}
diff --git a/modules/md/md_time.h b/modules/md/md_time.h
index 92bd9d8aa97..99c610aa88d 100644
--- a/modules/md/md_time.h
+++ b/modules/md/md_time.h
@@ -74,4 +74,7 @@ const char *md_timeslice_format(const md_timeslice_t *ts, apr_pool_t *p);
md_timeperiod_t md_timeperiod_slice_before_end(const md_timeperiod_t *period,
const md_timeslice_t *ts);
+/* parse rfc3339 timestamp, return 0 when not valid */
+apr_time_t md_time_parse_rfc3339(const char *s);
+
#endif /* md_util_h */
diff --git a/modules/md/md_version.h b/modules/md/md_version.h
index 5b02ed369a8..8ae950a457f 100644
--- a/modules/md/md_version.h
+++ b/modules/md/md_version.h
@@ -27,7 +27,7 @@
* @macro
* Version number of the md module as c string
*/
-#define MOD_MD_VERSION "2.5.2"
+#define MOD_MD_VERSION "2.6.1"
/**
* @macro
@@ -35,9 +35,8 @@
* release. This is a 24 bit number with 8 bits for major number, 8 bits
* for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
*/
-#define MOD_MD_VERSION_NUM 0x020502
+#define MOD_MD_VERSION_NUM 0x020601
#define MD_ACME_DEF_URL "https://acme-v02.api.letsencrypt.org/directory"
-#define MD_TAILSCALE_DEF_URL "file://localhost/var/run/tailscale/tailscaled.sock"
#endif /* mod_md_md_version_h */
diff --git a/modules/md/mod_md.c b/modules/md/mod_md.c
index 8b83b4e6786..349d18750c8 100644
--- a/modules/md/mod_md.c
+++ b/modules/md/mod_md.c
@@ -331,7 +331,8 @@ static void merge_srv_config(md_t *md, md_srv_conf_t *base_sc, apr_pool_t *p)
APR_ARRAY_PUSH(md->contacts, const char *) =
md_util_schemify(p, contact, "mailto");
}
- else if( md->sc->s->server_admin && strcmp(DEFAULT_ADMIN, md->sc->s->server_admin)) {
+ else if (md->sc->s->server_admin &&
+ strcmp(DEFAULT_ADMIN, md->sc->s->server_admin)) {
apr_array_clear(md->contacts);
APR_ARRAY_PUSH(md->contacts, const char *) =
md_util_schemify(p, md->sc->s->server_admin, "mailto");
@@ -369,6 +370,9 @@ static void merge_srv_config(md_t *md, md_srv_conf_t *base_sc, apr_pool_t *p)
if (md->profile_mandatory < 0) {
md->profile_mandatory = md_config_geti(md->sc, MD_CONFIG_CA_PROFILE_MANDATORY);
}
+ if (md->ari_renewals < 0) {
+ md->ari_renewals = md_config_geti(md->sc, MD_CONFIG_ARI_RENEWALS);
+ }
}
static apr_status_t check_coverage(md_t *md, const char *domain, server_rec *s,
diff --git a/modules/md/mod_md.dsp b/modules/md/mod_md.dsp
index d0365ed8531..7b247a5bc0d 100644
--- a/modules/md/mod_md.dsp
+++ b/modules/md/mod_md.dsp
@@ -205,10 +205,6 @@ SOURCE=./md_store_fs.c
# End Source File
# Begin Source File
-SOURCE=./md_tailscale.c
-# End Source File
-# Begin Source File
-
SOURCE=./md_time.c
# End Source File
# Begin Source File
diff --git a/modules/md/mod_md_config.c b/modules/md/mod_md_config.c
index 413f1e8e99b..70632b22ef8 100644
--- a/modules/md/mod_md_config.c
+++ b/modules/md/mod_md_config.c
@@ -85,7 +85,7 @@ static md_mod_conf_t defmc = {
"https://crt.sh?q=", /* default cert checker site url */
NULL, /* CA cert file to use */
apr_time_from_sec(MD_SECS_PER_DAY/2), /* default time between cert checks */
- apr_time_from_sec(5), /* minimum delay for retries */
+ apr_time_from_sec(30), /* minimum delay for retries */
13, /* retry_failover after 14 errors, with 5s delay ~ half a day */
0, /* store locks, disabled by default */
apr_time_from_sec(5), /* max time to wait to obaint a store lock */
@@ -124,6 +124,7 @@ static md_srv_conf_t defconf = {
0, /* ACME profile mandatory */
0, /* stapling */
1, /* staple others */
+ 1, /* ACME ARI renewals */
NULL, /* dns01_cmd */
NULL, /* currently defined md */
NULL, /* assigned md, post config */
@@ -181,6 +182,7 @@ static void srv_conf_props_clear(md_srv_conf_t *sc)
sc->profile_mandatory = DEF_VAL;
sc->stapling = DEF_VAL;
sc->staple_others = DEF_VAL;
+ sc->ari_renewals = DEF_VAL;
sc->dns01_cmd = NULL;
}
@@ -204,6 +206,7 @@ static void srv_conf_props_copy(md_srv_conf_t *to, const md_srv_conf_t *from)
to->profile_mandatory = from->profile_mandatory;
to->stapling = from->stapling;
to->staple_others = from->staple_others;
+ to->ari_renewals = from->ari_renewals;
to->dns01_cmd = from->dns01_cmd;
}
@@ -229,6 +232,7 @@ static void srv_conf_props_apply(md_t *md, const md_srv_conf_t *from, apr_pool_t
if (from->ca_eab_hmac) md->ca_eab_hmac = from->ca_eab_hmac;
if (from->profile) md->profile = from->profile;
if (from->profile_mandatory != DEF_VAL) md->profile_mandatory = from->profile_mandatory;
+ if (from->ari_renewals != DEF_VAL) md->ari_renewals = from->ari_renewals;
if (from->stapling != DEF_VAL) md->stapling = from->stapling;
if (from->dns01_cmd) md->dns01_cmd = from->dns01_cmd;
}
@@ -277,7 +281,7 @@ static void *md_config_merge(apr_pool_t *pool, void *basev, void *addv)
nsc->profile = add->profile? add->profile : base->profile;
nsc->profile_mandatory = (add->profile_mandatory != DEF_VAL)? add->profile_mandatory : base->profile_mandatory;
nsc->stapling = (add->stapling != DEF_VAL)? add->stapling : base->stapling;
- nsc->staple_others = (add->staple_others != DEF_VAL)? add->staple_others : base->staple_others;
+ nsc->ari_renewals = (add->ari_renewals != DEF_VAL)? add->ari_renewals : base->ari_renewals;
nsc->dns01_cmd = (add->dns01_cmd)? add->dns01_cmd : base->dns01_cmd;
nsc->current = NULL;
@@ -650,6 +654,18 @@ static const char *md_config_set_staple_others(cmd_parms *cmd, void *dc, const c
return set_on_off(&config->staple_others, value, cmd->pool);
}
+static const char *md_config_set_ari(cmd_parms *cmd, void *dc, const char *value)
+{
+ md_srv_conf_t *config = md_config_get(cmd->server);
+ const char *err;
+
+ (void)dc;
+ if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) {
+ return err;
+ }
+ return set_on_off(&config->ari_renewals, value, cmd->pool);
+}
+
static const char *md_config_set_base_server(cmd_parms *cmd, void *dc, const char *value)
{
md_srv_conf_t *config = md_config_get(cmd->server);
@@ -689,6 +705,9 @@ static const char *md_config_set_min_delay(cmd_parms *cmd, void *dc, const char
if (md_duration_parse(&delay, value, "s") != APR_SUCCESS) {
return "unrecognized duration format";
}
+ if (delay <= 0) {
+ return "minimum delay must be greater than 0";
+ }
config->mc->min_delay = delay;
return NULL;
}
@@ -1364,6 +1383,8 @@ const command_rec md_cmds[] = {
"The name of an CA profile to order certificates for."),
AP_INIT_TAKE1("MDProfileMandatory", md_config_set_profile_mandatory, NULL, RSRC_CONF,
"Determines if a configured CA profile is mandatory."),
+ AP_INIT_TAKE1("MDRenewViaARI", md_config_set_ari, NULL, RSRC_CONF,
+ "Enable/Disable ACME ARI (RFC 9773) to trigger renewals."),
AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL)
};
@@ -1458,6 +1479,8 @@ int md_config_geti(const md_srv_conf_t *sc, md_config_var_t var)
return (sc->staple_others != DEF_VAL)? sc->staple_others : defconf.staple_others;
case MD_CONFIG_CA_PROFILE_MANDATORY:
return (sc->profile_mandatory != DEF_VAL)? sc->profile_mandatory : defconf.profile_mandatory;
+ case MD_CONFIG_ARI_RENEWALS:
+ return (sc->ari_renewals != DEF_VAL)? sc->ari_renewals : defconf.ari_renewals;
default:
return 0;
}
diff --git a/modules/md/mod_md_config.h b/modules/md/mod_md_config.h
index 48272cfdf13..a2354354cc4 100644
--- a/modules/md/mod_md_config.h
+++ b/modules/md/mod_md_config.h
@@ -41,6 +41,7 @@ typedef enum {
MD_CONFIG_STAPLE_OTHERS,
MD_CONFIG_CA_PROFILE,
MD_CONFIG_CA_PROFILE_MANDATORY,
+ MD_CONFIG_ARI_RENEWALS,
} md_config_var_t;
typedef enum {
@@ -110,6 +111,7 @@ typedef struct md_srv_conf_t {
int stapling; /* OCSP stapling enabled */
int staple_others; /* Provide OCSP stapling for non-MD certificates */
+ int ari_renewals; /* ACME ARI extension enabled */
const char *dns01_cmd; /* DNS challenge command, override global command */
diff --git a/modules/md/mod_md_drive.c b/modules/md/mod_md_drive.c
index d2655b8a0c8..9dfa93a290c 100644
--- a/modules/md/mod_md_drive.c
+++ b/modules/md/mod_md_drive.c
@@ -105,22 +105,75 @@ static void process_drive_job(md_renew_ctx_t *dctx, md_job_t *job, apr_pool_t *p
* Only returns SUCCESS when the renewal is complete, e.g. STAGING has a
* complete set of new credentials.
*/
- ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10052)
+ apr_time_t renew_at, now;
+ const char *ari_explain_url = NULL;
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10052)
"md(%s): state=%d, driving", job->mdomain, md->state);
+ renew_at = md_reg_renew_at(dctx->mc->reg, md, ptemp);
+ now = apr_time_now();
+
if (md->stapling && dctx->mc->ocsp &&
- md_reg_has_revoked_certs(dctx->mc->reg, dctx->mc->ocsp, md, dctx->p)) {
- ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10500)
- "md(%s): has revoked certificates", job->mdomain);
+ md_reg_has_revoked_certs(dctx->mc->reg, dctx->mc->ocsp, md, ptemp)) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10500)
+ "md(%s): need to renew, OCSP reports revoked "
+ "certificate(s)", job->mdomain);
+ }
+ else if (!renew_at || renew_at <= now) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10512)
+ "md(%s): need to renew now", job->mdomain);
}
- else if (!md_reg_should_renew(dctx->mc->reg, md, dctx->p)) {
- ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10053)
- "md(%s): no need to renew", job->mdomain);
- goto expiry;
+ else {
+ apr_time_t ari_renew_at = 0;
+ char ts[APR_RFC822_DATE_LEN];
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10513)
+ "md(%s): ARI is %senabled", job->mdomain,
+ md->ari_renewals? "" : "not ");
+ if (md->ari_renewals)
+ ari_renew_at = md_reg_ari_renew_at(&ari_explain_url, dctx->mc->reg, md,
+ dctx->mc->env, result, ptemp);
+ if (!ari_renew_at || (ari_renew_at > renew_at)) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10514)
+ "md(%s): %sup for configured renewal in %s",
+ job->mdomain,
+ (ari_renew_at || !md->ari_renewals) ? "" : "no ARI available, ",
+ md_duration_print(ptemp, renew_at - now));
+ goto expiry;
+ }
+ else if (ari_renew_at > now) {
+ long secs = (long)apr_time_sec(ari_renew_at - now);
+ apr_rfc822_date(ts, ari_renew_at);
+ if (ari_explain_url) {
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, dctx->s, APLOGNO(10515)
+ "md(%s): CA advises renew via ARI at %s"
+ ", for explanation see %s",
+ job->mdomain, ts, ari_explain_url);
+ }
+ else
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10516)
+ "md(%s): CA advises renew via ARI at %s",
+ job->mdomain, ts);
+ if (secs < MD_SECS_PER_DAY) { /* earlier than regular run */
+ job->next_run = ari_renew_at;
+ }
+ goto expiry;
+ }
+ /* ARI says we should renew *now* */
+ if (ari_explain_url) {
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, dctx->s, APLOGNO(10517)
+ "md(%s): CA advises renew via ARI now"
+ ", for explanation see %s",
+ job->mdomain, ari_explain_url);
+ }
+ else
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10518)
+ "md(%s): CA advises renew via ARI now", job->mdomain);
}
/* The (possibly configured) event handler may veto renewals. This
- * is used in cluster installtations, see #233. */
+ * is used in cluster installations, see #233. */
rv = md_event_raise("renewing", md->name, job, result, ptemp);
if (APR_SUCCESS != rv) {
ap_log_error(APLOG_MARK, APLOG_INFO, 0, dctx->s, APLOGNO(10060)
@@ -130,6 +183,10 @@ static void process_drive_job(md_renew_ctx_t *dctx, md_job_t *job, apr_pool_t *p
}
md_job_start_run(job, result, md_reg_store_get(dctx->mc->reg));
+ if (ari_explain_url)
+ md_result_printf(result, 0,
+ "Renewal triggered by CA via ARI, explanation at %s",
+ ari_explain_url);
md_reg_renew(dctx->mc->reg, md, dctx->mc->env, 0, job->error_runs, result, ptemp);
md_job_end_run(job, result);
@@ -159,7 +216,7 @@ static void process_drive_job(md_renew_ctx_t *dctx, md_job_t *job, apr_pool_t *p
}
expiry:
- if (!job->finished && md_reg_should_warn(dctx->mc->reg, md, dctx->p)) {
+ if (!job->finished && md_reg_should_warn(dctx->mc->reg, md, ptemp)) {
ap_log_error( APLOG_MARK, APLOG_TRACE1, 0, dctx->s,
"md(%s): warn about expiration", md->name);
md_job_start_run(job, result, md_reg_store_get(dctx->mc->reg));
diff --git a/modules/md/mod_md_status.c b/modules/md/mod_md_status.c
index 72cff4180fc..c83e73d17aa 100644
--- a/modules/md/mod_md_status.c
+++ b/modules/md/mod_md_status.c
@@ -354,10 +354,8 @@ static void val_url_print(status_ctx *ctx, const status_info *info,
{
const char *s;
- if (proto && !strcmp(proto, "tailscale")) {
- s = "tailscale";
- }
- else if (url) {
+ (void)proto;
+ if (url) {
s = md_get_ca_name_from_url(ctx->p, url);
}
else {
diff --git a/test/modules/md/md_conf.py b/test/modules/md/md_conf.py
index 19d4977f004..54b10abc659 100755
--- a/test/modules/md/md_conf.py
+++ b/test/modules/md/md_conf.py
@@ -15,6 +15,8 @@ class MDConf(HttpdConf):
self.add_admin(admin)
self.add([
"MDRetryDelay 1s", # speed up testing a little
+ "MDRenewViaARI off", # not on, logs unwanted errors when test
+ # acme server is not responding
])
if local_ca:
self.add([
diff --git a/test/modules/md/test_702_auto.py b/test/modules/md/test_702_auto.py
index df24290f576..7246b62255c 100644
--- a/test/modules/md/test_702_auto.py
+++ b/test/modules/md/test_702_auto.py
@@ -299,7 +299,7 @@ class TestAutov2:
# Force cert renewal due to critical remaining valid duration
# Assert that new cert activation is delayed
- def test_md_702_009(self, env):
+ def test_md_702_009(self, env, acme):
domain = self.test_domain
domains = [domain]
#
@@ -329,6 +329,7 @@ class TestAutov2:
assert env.await_completion([domain], must_renew=True)
stat = env.get_certificate_status(domain)
assert creds.certificate.serial_number != int(stat['rsa']['serial'], 16)
+ env.httpd_error_log.clear_log()
# test case: drive with an unsupported challenge due to port availability
def test_md_702_010(self, env):
diff --git a/test/modules/md/test_710_profiles.py b/test/modules/md/test_710_profiles.py
index 2fbcf267ae3..a7faad64f0b 100644
--- a/test/modules/md/test_710_profiles.py
+++ b/test/modules/md/test_710_profiles.py
@@ -122,7 +122,7 @@ class TestProfiles:
assert stat["profile"] == "XXX", f'{stat}'
assert len(stat['cert']) == 0, f'{stat}'
assert stat['renewal']['errors'] > 0, f'{stat}'
- assert stat['renewal']['last']['activity'] == 'Creating new order', f'{stat}'
+ assert stat['renewal']['last']['activity'] == 'Creating new order, key-spec=default, profile=XXX, replacing-cert=none', f'{stat}'
MDConf(env).install()
assert env.apache_restart() == 0, f'{env.apachectl_stderr}'
env.httpd_error_log.ignore_recent(matches=[
diff --git a/test/modules/md/test_730_static.py b/test/modules/md/test_730_static.py
index 3c4d26e88c9..bc3f20ce6b3 100644
--- a/test/modules/md/test_730_static.py
+++ b/test/modules/md/test_730_static.py
@@ -58,6 +58,11 @@ class TestStatic:
assert 'cert' in stat
assert stat['renew'] is True
assert 'renewal' not in stat
+ env.httpd_error_log.ignore_recent(
+ matches = [
+ r'.*cert has no authority key id extension.*'
+ ]
+ )
def test_md_730_002(self, env):
# MD with static cert files, force driving
@@ -94,6 +99,11 @@ class TestStatic:
assert 'cert' in stat['renewal']
assert 'secp384r1' in stat['renewal']['cert']
assert 'rsa' in stat['renewal']['cert']
+ env.httpd_error_log.ignore_recent(
+ matches = [
+ r'.*cert has no authority key id extension.*'
+ ]
+ )
def test_md_730_003(self, env):
# just configuring one file will not work
diff --git a/test/modules/md/test_780_tailscale.py b/test/modules/md/test_780_tailscale.py
deleted file mode 100644
index bb218f90c18..00000000000
--- a/test/modules/md/test_780_tailscale.py
+++ /dev/null
@@ -1,198 +0,0 @@
-import os
-import re
-import socket
-import sys
-from threading import Thread
-
-import pytest
-
-from .md_conf import MDConf
-
-
-class TailscaleFaker:
-
- def __init__(self, env, path):
- self.env = env
- self._uds_path = path
- self._done = False
-
- def start(self):
- def process(self):
- self._socket.listen(1)
- self._process()
-
- try:
- os.unlink(self._uds_path)
- except OSError:
- if os.path.exists(self._uds_path):
- raise
- self._socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
- self._socket.bind(self._uds_path)
- self._thread = Thread(target=process, daemon=True, args=[self])
- self._thread.start()
-
- def stop(self):
- self._done = True
- self._socket.close()
-
- def send_error(self, c, status, reason):
- c.sendall(f"""HTTP/1.1 {status} {reason}\r
-Server: TailscaleFaker\r
-Content-Length: 0\r
-Connection: close\r
-\r
-""".encode())
-
- def send_data(self, c, ctype: str, data: bytes):
- c.sendall(f"""HTTP/1.1 200 OK\r
-Server: TailscaleFaker\r
-Content-Type: {ctype}\r
-Content-Length: {len(data)}\r
-Connection: close\r
-\r
-""".encode() + data)
-
- def _process(self):
- # a http server written on a sunny afternooon
- while self._done is False:
- try:
- c, client_address = self._socket.accept()
- try:
- data = c.recv(1024)
- lines = data.decode().splitlines()
- m = re.match(r'^(?P\w+)\s+(?P\S+)\s+HTTP/1.1', lines[0])
- if m is None:
- self.send_error(c, 400, "Bad Request")
- continue
- uri = m.group('uri')
- m = re.match(r'/localapi/v0/cert/(?P\S+)\?type=(?P\w+)', uri)
- if m is None:
- self.send_error(c, 404, "Not Found")
- continue
- domain = m.group('domain')
- cred_type = m.group('type')
- creds = self.env.get_credentials_for_name(domain)
- sys.stderr.write(f"lookup domain={domain}, type={cred_type} -> {creds}\n")
- if creds is None or len(creds) == 0:
- self.send_error(c, 404, "Not Found")
- continue
- if cred_type == 'crt':
- self.send_data(c, "text/plain", creds[0].cert_pem)
- pass
- elif cred_type == 'key':
- self.send_data(c, "text/plain", creds[0].pkey_pem)
- else:
- self.send_error(c, 404, "Not Found")
- continue
- finally:
- c.close()
-
- except ConnectionAbortedError:
- self._done = True
-
-
-class TestTailscale:
-
- @pytest.fixture(autouse=True, scope='class')
- def _class_scope(self, env, acme):
- UDS_PATH = f"{env.gen_dir}/tailscale.sock"
- TestTailscale.UDS_PATH = UDS_PATH
- faker = TailscaleFaker(env=env, path=UDS_PATH)
- faker.start()
- env.APACHE_CONF_SRC = "data/test_auto"
- acme.start(config='default')
- env.clear_store()
- MDConf(env).install()
- assert env.apache_restart() == 0, f'{env.apachectl_stderr}'
- yield
- faker.stop()
-
- @pytest.fixture(autouse=True, scope='function')
- def _method_scope(self, env, request):
- env.clear_store()
- self.test_domain = env.get_request_domain(request)
-
- def _write_res_file(self, doc_root, name, content):
- if not os.path.exists(doc_root):
- os.makedirs(doc_root)
- open(os.path.join(doc_root, name), "w").write(content)
-
- # create a MD using `tailscale` as protocol, wrong path
- def test_md_780_001(self, env):
- domain = env.tailscale_domain
- # generate config with one MD
- domains = [domain]
- socket_path = '/xxx'
- conf = MDConf(env, admin="admin@" + domain)
- conf.start_md(domains)
- conf.add([
- "MDCertificateProtocol tailscale",
- f"MDCertificateAuthority file://{socket_path}",
- ])
- conf.end_md()
- conf.add_vhost(domains)
- conf.install()
- # restart and watch it fail due to wrong tailscale unix socket path
- assert env.apache_restart() == 0, f'{env.apachectl_stderr}'
- md = env.await_error(domain)
- assert md
- assert md['renewal']['errors'] > 0
- assert md['renewal']['last']['status-description'] == 'No such file or directory'
- assert md['renewal']['last']['detail'] == \
- f"tailscale socket not available, may not be up: {socket_path}"
- #
- env.httpd_error_log.ignore_recent(
- lognos = [
- "AH10056" # retrieving certificate from tailscale
- ]
- )
-
- # create a MD using `tailscale` as protocol, path to faker, should succeed
- def test_md_780_002(self, env):
- domain = env.tailscale_domain
- # generate config with one MD
- domains = [domain]
- socket_path = '/xxx'
- conf = MDConf(env, admin="admin@" + domain)
- conf.start_md(domains)
- conf.add([
- "MDCertificateProtocol tailscale",
- f"MDCertificateAuthority file://{self.UDS_PATH}",
- ])
- conf.end_md()
- conf.add_vhost(domains)
- conf.install()
- # restart and watch it fail due to wrong tailscale unix socket path
- assert env.apache_restart() == 0, f'{env.apachectl_stderr}'
- assert env.await_completion(domains)
- assert env.apache_restart() == 0, f'{env.apachectl_stderr}'
- env.check_md_complete(domain)
-
- # create a MD using `tailscale` as protocol, but domain name not assigned by tailscale
- def test_md_780_003(self, env):
- domain = "test.not-correct.ts.net"
- # generate config with one MD
- domains = [domain]
- socket_path = '/xxx'
- conf = MDConf(env, admin="admin@" + domain)
- conf.start_md(domains)
- conf.add([
- "MDCertificateProtocol tailscale",
- f"MDCertificateAuthority file://{self.UDS_PATH}",
- ])
- conf.end_md()
- conf.add_vhost(domains)
- conf.install()
- # restart and watch it fail due to wrong tailscale unix socket path
- assert env.apache_restart() == 0, f'{env.apachectl_stderr}'
- md = env.await_error(domain)
- assert md
- assert md['renewal']['errors'] > 0
- assert md['renewal']['last']['status-description'] == 'No such file or directory'
- assert md['renewal']['last']['detail'] == "retrieving certificate from tailscale"
- #
- env.httpd_error_log.ignore_recent(
- lognos = [
- "AH10056" # retrieving certificate from tailscale
- ]
- )
diff --git a/test/modules/md/test_920_status.py b/test/modules/md/test_920_status.py
index d9babb172b3..a74ac3c1df6 100644
--- a/test/modules/md/test_920_status.py
+++ b/test/modules/md/test_920_status.py
@@ -215,6 +215,11 @@ Protocols h2 http/1.1 acme-tls/1
print(status)
assert status['state'] == env.MD_S_COMPLETE
assert status['renew-mode'] == 0 # manual
+ env.httpd_error_log.ignore_recent(
+ matches = [
+ r'.*cert has no authority key id extension.*'
+ ]
+ )
# MD with 2 certificates
def test_md_920_020(self, env):
--
2.47.2