Mailing List Archive

svn commit: r1900950 - in /httpd/httpd/branches/2.4.x: ./ changes-entries/ docs/manual/mod/ modules/md/ test/modules/md/
Author: icing
Date: Mon May 16 11:36:20 2022
New Revision: 1900950

URL: http://svn.apache.org/viewvc?rev=1900950&view=rev
Log:
Merge /httpd/httpd/trunk:r1900852,1900887

*) mod_md: the `MDCertificateAuthority` directive can take more than one URL/name of
an ACME CA. This gives a failover for renewals when several consecutive attempts
to get a certificate failed.
A new directive was added: `MDRetryDelay` sets the delay of retries.
A new directive was added: `MDRetryFailover` sets the number of errored
attempts before an alternate CA is selected for certificate renewals.


Added:
httpd/httpd/branches/2.4.x/changes-entries/md_acme_failover.txt
- copied unchanged from r1900852, httpd/httpd/trunk/changes-entries/md_acme_failover.txt
httpd/httpd/branches/2.4.x/test/modules/md/test_790_failover.py
- copied unchanged from r1900852, httpd/httpd/trunk/test/modules/md/test_790_failover.py
Modified:
httpd/httpd/branches/2.4.x/ (props changed)
httpd/httpd/branches/2.4.x/docs/manual/mod/mod_md.xml
httpd/httpd/branches/2.4.x/modules/md/md.h
httpd/httpd/branches/2.4.x/modules/md/md_acme_acct.c
httpd/httpd/branches/2.4.x/modules/md/md_acme_drive.c
httpd/httpd/branches/2.4.x/modules/md/md_core.c
httpd/httpd/branches/2.4.x/modules/md/md_ocsp.c
httpd/httpd/branches/2.4.x/modules/md/md_ocsp.h
httpd/httpd/branches/2.4.x/modules/md/md_reg.c
httpd/httpd/branches/2.4.x/modules/md/md_reg.h
httpd/httpd/branches/2.4.x/modules/md/md_status.c
httpd/httpd/branches/2.4.x/modules/md/md_status.h
httpd/httpd/branches/2.4.x/modules/md/md_tailscale.c
httpd/httpd/branches/2.4.x/modules/md/md_version.h
httpd/httpd/branches/2.4.x/modules/md/mod_md.c
httpd/httpd/branches/2.4.x/modules/md/mod_md_config.c
httpd/httpd/branches/2.4.x/modules/md/mod_md_config.h
httpd/httpd/branches/2.4.x/modules/md/mod_md_drive.c
httpd/httpd/branches/2.4.x/modules/md/mod_md_status.c
httpd/httpd/branches/2.4.x/test/modules/md/md_conf.py
httpd/httpd/branches/2.4.x/test/modules/md/md_env.py
httpd/httpd/branches/2.4.x/test/modules/md/test_001_store.py
httpd/httpd/branches/2.4.x/test/modules/md/test_100_reg_add.py
httpd/httpd/branches/2.4.x/test/modules/md/test_110_reg_update.py
httpd/httpd/branches/2.4.x/test/modules/md/test_120_reg_list.py
httpd/httpd/branches/2.4.x/test/modules/md/test_300_conf_validate.py
httpd/httpd/branches/2.4.x/test/modules/md/test_702_auto.py

Propchange: httpd/httpd/branches/2.4.x/
------------------------------------------------------------------------------
Merged /httpd/httpd/trunk:r1900852,1900887

Modified: httpd/httpd/branches/2.4.x/docs/manual/mod/mod_md.xml
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/docs/manual/mod/mod_md.xml?rev=1900950&r1=1900949&r2=1900950&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/docs/manual/mod/mod_md.xml (original)
+++ httpd/httpd/branches/2.4.x/docs/manual/mod/mod_md.xml Mon May 16 11:36:20 2022
@@ -471,27 +471,34 @@ MDomain example2.org auto

<directivesynopsis>
<name>MDCertificateAuthority</name>
- <description>The URL of the ACME Certificate Authority service.</description>
+ <description>The URL(s) of the ACME Certificate Authority to use.</description>
<syntax>MDCertificateAuthority <var>url</var></syntax>
- <default>MDCertificateAuthority https://acme-v02.api.letsencrypt.org/directory</default>
+ <default>MDCertificateAuthority letsencrypt</default>
<contextlist>
<context>server config</context>
</contextlist>
<usage>
<p>
- The URL where the CA offers its service.
+ The URL(s) where the CA offers its service.
+ Instead of the actual URL, you may use 'letsencrypt' or 'buypass'.
</p><p>
- Let's Encrypt offers, right now, four such URLs. Two for
- the own legacy version of the ACME protocol, commonly named ACMEv1.
- And two for the RFC 8555 version, named ACMEv2.
+ If you configure more than one URL, each one is tried in a round-robin
+ fashion after a number of failures. You can configure how quickly or
+ delayed that happens via the <directive>MDRetryDelay</directive> and
+ <directive>MDRetryFailover</directive> directives. The default setting
+ makes a failover after about half a day of trying.
</p><p>
- Each version has 2 endpoints, as their is a production endpoint and a
- "staging" endpoint for testing. The testing endpoint works the same, but will
- not give you certificates recognized by browsers. However, it also has
- very relaxed rate limits. This allows testing of the service repeatedly
- without you blocking yourself.
+ All other settings apply to each of these URLs. It is therefore
+ not possible to have two with different
+ <directive>MDExternalAccountBinding</directive>s, for example.
+ </p><p>
+ For testing, CAs commonly offer a second service URL.
+ The 'test' service does not give certificates valid in a browser,
+ but are more relaxed in regard to rate limits.
+ This allows for verfication of your own setup before switching
+ to the production service URL.
</p>
- <example><title>LE Staging Setup</title>
+ <example><title>LE Test Setup</title>
<highlight language="config">
MDCertificateAuthority https://acme-staging-v02.api.letsencrypt.org/directory
</highlight>
@@ -1375,5 +1382,46 @@ MDMessageCmd /etc/apache/md-message
</p>
</usage>
</directivesynopsis>
+
+ <directivesynopsis>
+ <name>MDRetryDelay</name>
+ <description></description>
+ <syntax>MDRetryDelay <var>duration</var></syntax>
+ <default>MDRetryDelay 5s</default>
+ <contextlist>
+ <context>server config</context>
+ </contextlist>
+ <compatibility>Available in version 2.4.54 and later</compatibility>
+ <usage>
+ <p>
+ The amount of time to wait after an error before trying
+ to renew a certificate again. This duration is doubled after
+ each consecutive error with a maximum of 24 hours.
+ </p>
+ <p>
+ It is kept separate for each certificate renewal. Meaning an error
+ on one MDomain does not delay the renewals of other domains.
+ </p>
+ </usage>
+ </directivesynopsis>
+
+ <directivesynopsis>
+ <name>MDRetryFailover</name>
+ <description></description>
+ <syntax>MDRetryFailover <var>number</var></syntax>
+ <default>MDRetryFailover 13</default>
+ <contextlist>
+ <context>server config</context>
+ </contextlist>
+ <compatibility>Available in version 2.4.54 and later</compatibility>
+ <usage>
+ <p>
+ The number of consecutive errors on renewing a certificate before
+ another CA is selected. This only applies to configurations that
+ have more than one <directive>MDCertificateAuthority</directive>
+ specified.
+ </p>
+ </usage>
+ </directivesynopsis>

</modulesynopsis>

Modified: httpd/httpd/branches/2.4.x/modules/md/md.h
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md.h?rev=1900950&r1=1900949&r2=1900950&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md.h (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md.h Mon May 16 11:36:20 2022
@@ -87,8 +87,9 @@ struct md_t {
md_timeslice_t *renew_window; /* time before expiration that starts renewal */
md_timeslice_t *warn_window; /* time before expiration that warnings are sent out */

- const char *ca_url; /* url of CA certificate service */
const char *ca_proto; /* protocol used vs CA (e.g. ACME) */
+ struct apr_array_header_t *ca_urls; /* urls of CAs */
+ const char *ca_effective; /* url of CA used */
const char *ca_account; /* account used at CA */
const char *ca_agreement; /* accepted agreement uri between CA and user */
struct apr_array_header_t *ca_challenges; /* challenge types configured for this MD */
@@ -203,6 +204,7 @@ struct md_t {
#define MD_KEY_UNKNOWN "unknown"
#define MD_KEY_UNTIL "until"
#define MD_KEY_URL "url"
+#define MD_KEY_URLS "urls"
#define MD_KEY_URI "uri"
#define MD_KEY_VALID "valid"
#define MD_KEY_VALID_FROM "valid-from"

Modified: httpd/httpd/branches/2.4.x/modules/md/md_acme_acct.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md_acme_acct.c?rev=1900950&r1=1900949&r2=1900950&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md_acme_acct.c (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md_acme_acct.c Mon May 16 11:36:20 2022
@@ -243,7 +243,7 @@ int md_acme_acct_matches_url(md_acme_acc

int md_acme_acct_matches_md(md_acme_acct_t *acct, const md_t *md)
{
- if (!md_acme_acct_matches_url(acct, md->ca_url)) return 0;
+ if (!md_acme_acct_matches_url(acct, md->ca_effective)) return 0;
/* if eab values are not mentioned, we match an account regardless
* if it was registered with eab or not */
if (!md->ca_eab_kid || !md->ca_eab_hmac) {
@@ -285,7 +285,7 @@ static int find_acct(void *baton, const
&& (!ctx->md || md_acme_acct_matches_md(acct, ctx->md))) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ctx->p,
"found account %s for %s: %s, status=%d",
- acct->id, ctx->md->ca_url, aspect, acct->status);
+ acct->id, ctx->md->ca_effective, aspect, acct->status);
ctx->id = apr_pstrdup(ctx->p, name);
return 0;
}

Modified: httpd/httpd/branches/2.4.x/modules/md/md_acme_drive.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md_acme_drive.c?rev=1900950&r1=1900949&r2=1900950&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md_acme_drive.c (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md_acme_drive.c Mon May 16 11:36:20 2022
@@ -45,7 +45,7 @@
/**************************************************************************************************/
/* account setup */

-static apr_status_t use_staged_acct(md_acme_t *acme, struct md_store_t *store,
+static apr_status_t use_staged_acct(md_acme_t *acme, struct md_store_t *store,
const md_t *md, apr_pool_t *p)
{
md_acme_acct_t *acct;
@@ -654,13 +654,15 @@ static apr_status_t acme_renew(md_proto_
apr_status_t rv = APR_SUCCESS;
apr_time_t now, t, t2;
md_credentials_t *cred;
+ const char *ca_effective = NULL;
char ts[APR_RFC822_DATE_LEN];
int i, first = 0;
-
- if (md_log_is_level(d->p, MD_LOG_DEBUG)) {
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: staging started, "
- "state=%d, challenges='%s'", d->md->name, d->md->state,
- apr_array_pstrcat(d->p, ad->ca_challenges, ' '));
+
+ 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_result_printf(result, APR_INCOMPLETE,
+ "The managed domain %s is missing MDCertificateAuthority", d->md->name);
+ goto out;
}

/* When not explicitly told to reset, we check the existing data. If
@@ -679,13 +681,46 @@ static apr_status_t acme_renew(md_proto_
rv = APR_SUCCESS;
}
}
-
+
+ /* What CA are we using this time? */
+ if (ad->md && ad->md->ca_effective) {
+ /* There was one chosen on the previous run. Do we stick to it? */
+ ca_effective = ad->md->ca_effective;
+ if (d->md->ca_urls->nelts > 1 && d->attempt >= d->retry_failover) {
+ /* We have more than one CA to choose from and this is the (at least)
+ * third attempt with the same CA. Let's switch to the next one. */
+ int last_idx = md_array_str_index(d->md->ca_urls, ca_effective, 0, 1);
+ if (last_idx >= 0) {
+ int next_idx = (last_idx+1) % d->md->ca_urls->nelts;
+ ca_effective = APR_ARRAY_IDX(d->md->ca_urls, next_idx, const char*);
+ }
+ else {
+ /* not part of current configuration? */
+ ca_effective = NULL;
+ }
+ /* switching CA means we need to wipe the staging area */
+ reset_staging = 1;
+ }
+ }
+
+ if (!ca_effective) {
+ /* None chosen yet, pick the first one configured */
+ ca_effective = APR_ARRAY_IDX(d->md->ca_urls, 0, const char*);
+ }
+
+ if (md_log_is_level(d->p, MD_LOG_DEBUG)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: staging started, "
+ "state=%d, attempt=%d, acme=%s, challenges='%s'",
+ d->md->name, d->md->state, d->attempt, ca_effective,
+ apr_array_pstrcat(d->p, ad->ca_challenges, ' '));
+ }
+
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, will", d->md->name);
+ "%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 out;
@@ -709,24 +744,14 @@ static apr_status_t acme_renew(md_proto_
}

/* Need to renew */
- md_result_activity_printf(result, "Contacting ACME server for %s at %s",
- d->md->name, d->md->ca_url);
- if (APR_SUCCESS != (rv = md_acme_create(&ad->acme, d->p, d->md->ca_url, d->proxy_url, d->ca_file))) {
- md_result_printf(result, rv, "setup ACME communications");
- md_result_log(result, MD_LOG_ERR);
- goto out;
- }
- if (APR_SUCCESS != (rv = md_acme_setup(ad->acme, result))) {
- md_result_log(result, MD_LOG_ERR);
- goto out;
- }
-
- if (!ad->md || strcmp(ad->md->ca_url, d->md->ca_url)) {
+ if (!ad->md || !md_array_str_eq(ad->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);
ad->md = md_copy(d->p, d->md);
+ ad->md->ca_effective = ca_effective;
+ ad->md->ca_account = NULL;
ad->order = NULL;
rv = md_save(d->store, d->p, MD_SG_STAGING, ad->md, 0);
if (APR_SUCCESS != rv) {
@@ -739,6 +764,19 @@ static apr_status_t acme_renew(md_proto_
ad->domains = md_dns_make_minimal(d->p, ad->md->domains);
}

+ md_result_activity_printf(result, "Contacting ACME server for %s at %s",
+ d->md->name, ca_effective);
+ if (APR_SUCCESS != (rv = md_acme_create(&ad->acme, d->p, ca_effective,
+ d->proxy_url, d->ca_file))) {
+ md_result_printf(result, rv, "setup ACME communications");
+ md_result_log(result, MD_LOG_ERR);
+ goto out;
+ }
+ if (APR_SUCCESS != (rv = md_acme_setup(ad->acme, result))) {
+ md_result_log(result, MD_LOG_ERR);
+ goto out;
+ }
+
if (APR_SUCCESS != load_missing_creds(d)) {
for (i = 0; i < ad->creds->nelts; ++i) {
ad->cred = APR_ARRAY_IDX(ad->creds, i, md_credentials_t*);
@@ -922,7 +960,12 @@ static apr_status_t acme_preload(md_prot
md_result_set(result, rv, "loading staged md.json");
goto leave;
}
-
+ if (!md->ca_effective) {
+ rv = APR_ENOENT;
+ md_result_set(result, rv, "effective CA url not set");
+ goto leave;
+ }
+
all_creds = apr_array_make(d->p, 5, sizeof(md_credentials_t*));
for (i = 0; i < md_pkeys_spec_count(md->pks); ++i) {
pkspec = md_pkeys_spec_get(md->pks, i);
@@ -985,7 +1028,8 @@ static apr_status_t acme_preload(md_prot
}
}

- if (APR_SUCCESS != (rv = md_acme_create(&acme, d->p, md->ca_url, d->proxy_url, d->ca_file))) {
+ if (APR_SUCCESS != (rv = md_acme_create(&acme, d->p, md->ca_effective,
+ d->proxy_url, d->ca_file))) {
md_result_set(result, rv, "error setting up acme");
goto leave;
}
@@ -1039,8 +1083,9 @@ static apr_status_t acme_driver_preload(
static apr_status_t acme_complete_md(md_t *md, apr_pool_t *p)
{
(void)p;
- if (!md->ca_url) {
- md->ca_url = MD_ACME_DEF_URL;
+ if (!md->ca_urls || apr_is_empty_array(md->ca_urls)) {
+ md->ca_urls = apr_array_make(p, 3, sizeof(const char *));
+ APR_ARRAY_PUSH(md->ca_urls, const char*) = MD_ACME_DEF_URL;
}
return APR_SUCCESS;
}

Modified: httpd/httpd/branches/2.4.x/modules/md/md_core.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md_core.c?rev=1900950&r1=1900949&r2=1900950&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md_core.c (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md_core.c Mon May 16 11:36:20 2022
@@ -241,8 +241,11 @@ md_t *md_clone(apr_pool_t *p, const md_t
md->renew_window = src->renew_window;
md->warn_window = src->warn_window;
md->contacts = md_array_str_clone(p, src->contacts);
- if (src->ca_url) md->ca_url = apr_pstrdup(p, src->ca_url);
if (src->ca_proto) md->ca_proto = apr_pstrdup(p, src->ca_proto);
+ if (src->ca_urls) {
+ md->ca_urls = md_array_str_clone(p, src->ca_urls);
+ }
+ if (src->ca_effective) md->ca_effective = apr_pstrdup(p, src->ca_effective);
if (src->ca_account) md->ca_account = apr_pstrdup(p, src->ca_account);
if (src->ca_agreement) md->ca_agreement = apr_pstrdup(p, src->ca_agreement);
if (src->defn_name) md->defn_name = apr_pstrdup(p, src->defn_name);
@@ -272,7 +275,10 @@ md_json_t *md_to_json(const md_t *md, ap
md_json_setl(md->transitive, json, MD_KEY_TRANSITIVE, NULL);
md_json_sets(md->ca_account, json, MD_KEY_CA, MD_KEY_ACCOUNT, NULL);
md_json_sets(md->ca_proto, json, MD_KEY_CA, MD_KEY_PROTO, NULL);
- md_json_sets(md->ca_url, json, MD_KEY_CA, MD_KEY_URL, NULL);
+ md_json_sets(md->ca_effective, json, MD_KEY_CA, MD_KEY_URL, NULL);
+ if (md->ca_urls && !apr_is_empty_array(md->ca_urls)) {
+ md_json_setsa(md->ca_urls, json, MD_KEY_CA, MD_KEY_URLS, NULL);
+ }
md_json_sets(md->ca_agreement, json, MD_KEY_CA, MD_KEY_AGREEMENT, NULL);
if (!md_pkeys_spec_is_empty(md->pks)) {
md_json_setj(md_pkeys_spec_to_json(md->pks, p), json, MD_KEY_PKEY, NULL);
@@ -324,7 +330,16 @@ md_t *md_from_json(md_json_t *json, apr_
md_json_dupsa(md->contacts, p, json, MD_KEY_CONTACTS, NULL);
md->ca_account = md_json_dups(p, json, MD_KEY_CA, MD_KEY_ACCOUNT, NULL);
md->ca_proto = md_json_dups(p, json, MD_KEY_CA, MD_KEY_PROTO, NULL);
- md->ca_url = md_json_dups(p, json, MD_KEY_CA, MD_KEY_URL, NULL);
+ md->ca_effective = md_json_dups(p, json, MD_KEY_CA, MD_KEY_URL, NULL);
+ if (md_json_has_key(json, MD_KEY_CA, MD_KEY_URLS, NULL)) {
+ md->ca_urls = apr_array_make(p, 5, sizeof(const char*));
+ md_json_dupsa(md->ca_urls, p, json, MD_KEY_CA, MD_KEY_URLS, NULL);
+ }
+ else if (md->ca_effective) {
+ /* compat for old format where we had only a single url */
+ md->ca_urls = apr_array_make(p, 5, sizeof(const char*));
+ APR_ARRAY_PUSH(md->ca_urls, const char*) = md->ca_effective;
+ }
md->ca_agreement = md_json_dups(p, json, MD_KEY_CA, MD_KEY_AGREEMENT, NULL);
if (md_json_has_key(json, MD_KEY_PKEY, NULL)) {
md->pks = md_pkeys_spec_from_json(md_json_getj(json, MD_KEY_PKEY, NULL), p);

Modified: httpd/httpd/branches/2.4.x/modules/md/md_ocsp.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md_ocsp.c?rev=1900950&r1=1900949&r2=1900950&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md_ocsp.c (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md_ocsp.c Mon May 16 11:36:20 2022
@@ -65,6 +65,7 @@ struct md_ocsp_reg_t {
md_timeslice_t renew_window;
md_job_notify_cb *notify;
void *notify_ctx;
+ apr_time_t min_delay;
};

typedef struct md_ocsp_status_t md_ocsp_status_t;
@@ -279,7 +280,8 @@ static apr_status_t ocsp_reg_cleanup(voi

apr_status_t md_ocsp_reg_make(md_ocsp_reg_t **preg, apr_pool_t *p, md_store_t *store,
const md_timeslice_t *renew_window,
- const char *user_agent, const char *proxy_url)
+ const char *user_agent, const char *proxy_url,
+ apr_time_t min_delay)
{
md_ocsp_reg_t *reg;
apr_status_t rv = APR_SUCCESS;
@@ -296,6 +298,7 @@ apr_status_t md_ocsp_reg_make(md_ocsp_re
reg->id_by_external_id = apr_hash_make(p);
reg->ostat_by_id = apr_hash_make(p);
reg->renew_window = *renew_window;
+ reg->min_delay = min_delay;

rv = apr_thread_mutex_create(&reg->mutex, APR_THREAD_MUTEX_NESTED, p);
if (APR_SUCCESS != rv) goto cleanup;
@@ -1056,5 +1059,5 @@ void md_ocsp_get_status_all(md_json_t **

md_job_t *md_ocsp_job_make(md_ocsp_reg_t *ocsp, const char *mdomain, apr_pool_t *p)
{
- return md_job_make(p, ocsp->store, MD_SG_OCSP, mdomain);
+ return md_job_make(p, ocsp->store, MD_SG_OCSP, mdomain, ocsp->min_delay);
}

Modified: httpd/httpd/branches/2.4.x/modules/md/md_ocsp.h
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md_ocsp.h?rev=1900950&r1=1900949&r2=1900950&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md_ocsp.h (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md_ocsp.h Mon May 16 11:36:20 2022
@@ -38,7 +38,8 @@ typedef struct md_ocsp_reg_t md_ocsp_reg
apr_status_t md_ocsp_reg_make(md_ocsp_reg_t **preg, apr_pool_t *p,
struct md_store_t *store,
const md_timeslice_t *renew_window,
- const char *user_agent, const char *proxy_url);
+ const char *user_agent, const char *proxy_url,
+ apr_time_t min_delay);

apr_status_t md_ocsp_init_id(struct md_data_t *id, apr_pool_t *p, const md_cert_t *cert);


Modified: httpd/httpd/branches/2.4.x/modules/md/md_reg.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md_reg.c?rev=1900950&r1=1900949&r2=1900950&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md_reg.c (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md_reg.c Mon May 16 11:36:20 2022
@@ -53,6 +53,8 @@ struct md_reg_t {
md_timeslice_t *warn_window;
md_job_notify_cb *notify;
void *notify_ctx;
+ apr_time_t min_delay;
+ int retry_failover;
};

/**************************************************************************************************/
@@ -80,7 +82,8 @@ static apr_status_t load_props(md_reg_t
}

apr_status_t md_reg_create(md_reg_t **preg, apr_pool_t *p, struct md_store_t *store,
- const char *proxy_url, const char *ca_file)
+ const char *proxy_url, const char *ca_file,
+ apr_time_t min_delay, int retry_failover)
{
md_reg_t *reg;
apr_status_t rv;
@@ -95,6 +98,8 @@ apr_status_t md_reg_create(md_reg_t **pr
reg->proxy_url = proxy_url? apr_pstrdup(p, proxy_url) : NULL;
reg->ca_file = (ca_file && apr_strnatcasecmp("none", ca_file))?
apr_pstrdup(p, ca_file) : NULL;
+ reg->min_delay = min_delay;
+ reg->retry_failover = retry_failover;

md_timeslice_create(&reg->renew_window, p, MD_TIME_LIFE_NORM, MD_TIME_RENEW_WINDOW_DEF);
md_timeslice_create(&reg->warn_window, p, MD_TIME_LIFE_NORM, MD_TIME_WARN_WINDOW_DEF);
@@ -165,12 +170,17 @@ static apr_status_t check_values(md_reg_
}
}

- if ((MD_UPD_CA_URL & fields) && md->ca_url) { /* setting to empty is ok */
- rv = md_util_abs_uri_check(p, md->ca_url, &err);
- if (err) {
- md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, p,
- "CA url for %s invalid (%s): %s", md->name, err, md->ca_url);
- return APR_EINVAL;
+ if ((MD_UPD_CA_URL & fields) && md->ca_urls) { /* setting to empty is ok */
+ int i;
+ const char *url;
+ for (i = 0; i < md->ca_urls->nelts; ++i) {
+ url = APR_ARRAY_IDX(md->ca_urls, i, const char*);
+ rv = md_util_abs_uri_check(p, url, &err);
+ if (err) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, p,
+ "CA url for %s invalid (%s): %s", md->name, err, url);
+ return APR_EINVAL;
+ }
}
}

@@ -451,7 +461,8 @@ static apr_status_t p_md_update(void *ba
md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update domains: %s", name);
}
if (MD_UPD_CA_URL & fields) {
- nmd->ca_url = updates->ca_url;
+ nmd->ca_urls = (updates->ca_urls?
+ apr_array_copy(p, updates->ca_urls) : NULL);
md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update ca url: %s", name);
}
if (MD_UPD_CA_PROTO & fields) {
@@ -934,13 +945,16 @@ apr_status_t md_reg_sync_finish(md_reg_t
md->ca_challenges = md_array_str_compact(p, md->ca_challenges, 0);
}
}
+ if (!md->ca_effective && old->ca_effective) {
+ md->ca_effective = apr_pstrdup(p, old->ca_effective);
+ }
if (!md->ca_account && old->ca_account) {
md->ca_account = apr_pstrdup(p, old->ca_account);
}

/* if everything remains the same, spare the write back */
if (!MD_VAL_UPDATE(md, old, state)
- && !MD_SVAL_UPDATE(md, old, ca_url)
+ && md_array_str_eq(md->ca_urls, old->ca_urls, 0)
&& !MD_SVAL_UPDATE(md, old, ca_proto)
&& !MD_SVAL_UPDATE(md, old, ca_agreement)
&& !MD_VAL_UPDATE(md, old, transitive)
@@ -1118,8 +1132,9 @@ apr_status_t md_reg_test_init(md_reg_t *

static apr_status_t run_renew(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
{
+ md_reg_t *reg = baton;
const md_t *md;
- int reset;
+ int reset, attempt;
md_proto_driver_t *driver;
apr_table_t *env;
apr_status_t rv;
@@ -1129,12 +1144,15 @@ static apr_status_t run_renew(void *bato
md = va_arg(ap, const md_t *);
env = va_arg(ap, apr_table_t *);
reset = va_arg(ap, int);
- result = va_arg(ap, md_result_t *);
+ attempt = va_arg(ap, int);
+ result = va_arg(ap, md_result_t *);

- rv = run_init(baton, ptemp, &driver, md, 0, env, result, NULL);
+ rv = run_init(reg, ptemp, &driver, md, 0, env, result, NULL);
if (APR_SUCCESS == rv) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp, "%s: run staging", md->name);
driver->reset = reset;
+ driver->attempt = attempt;
+ driver->retry_failover = reg->retry_failover;
rv = driver->proto->renew(driver, result);
}
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "%s: staging done", md->name);
@@ -1142,9 +1160,10 @@ static apr_status_t run_renew(void *bato
}

apr_status_t md_reg_renew(md_reg_t *reg, const md_t *md, apr_table_t *env,
- int reset, md_result_t *result, apr_pool_t *p)
+ int reset, int attempt,
+ md_result_t *result, apr_pool_t *p)
{
- return md_util_pool_vdo(run_renew, reg, p, md, env, reset, result, NULL);
+ return md_util_pool_vdo(run_renew, reg, p, md, env, reset, attempt, result, NULL);
}

static apr_status_t run_load_staging(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
@@ -1249,5 +1268,5 @@ void md_reg_set_warn_window_default(md_r

md_job_t *md_reg_job_make(md_reg_t *reg, const char *mdomain, apr_pool_t *p)
{
- return md_job_make(p, reg->store, MD_SG_STAGING, mdomain);
+ return md_job_make(p, reg->store, MD_SG_STAGING, mdomain, reg->min_delay);
}

Modified: httpd/httpd/branches/2.4.x/modules/md/md_reg.h
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md_reg.h?rev=1900950&r1=1900949&r2=1900950&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md_reg.h (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md_reg.h Mon May 16 11:36:20 2022
@@ -36,7 +36,8 @@ typedef struct md_reg_t md_reg_t;
* Create the MD registry, using the pool and store.
*/
apr_status_t md_reg_create(md_reg_t **preg, apr_pool_t *pm, md_store_t *store,
- const char *proxy_url, const char *ca_file);
+ const char *proxy_url, const char *ca_file,
+ apr_time_t min_delay, int retry_failover);

md_store_t *md_reg_store_get(md_reg_t *reg);

@@ -212,6 +213,8 @@ struct md_proto_driver_t {
int can_http;
int can_https;
int reset;
+ int attempt;
+ int retry_failover;
apr_interval_time_t activation_delay;
};

@@ -242,11 +245,17 @@ apr_status_t md_reg_test_init(md_reg_t *

/**
* Obtain new credentials for the given managed domain in STAGING.
- *
+ * @param reg the registry instance
+ * @param md the mdomain to renew
+ * @param env global environment of settings
+ * @param reset != 0 if any previous, partial information should be wiped
+ * @param attempt the number of attempts made this far (for this md)
+ * @param result for reporting results of the renewal
+ * @param p the memory pool to use
* @return APR_SUCCESS if new credentials have been staged successfully
*/
apr_status_t md_reg_renew(md_reg_t *reg, const md_t *md,
- struct apr_table_t *env, int reset,
+ struct apr_table_t *env, int reset, int attempt,
struct md_result_t *result, apr_pool_t *p);

/**

Modified: httpd/httpd/branches/2.4.x/modules/md/md_status.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md_status.c?rev=1900950&r1=1900949&r2=1900950&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md_status.c (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md_status.c Mon May 16 11:36:20 2022
@@ -286,7 +286,8 @@ apr_status_t md_status_get_json(md_json_
/* drive job persistence */

md_job_t *md_job_make(apr_pool_t *p, md_store_t *store,
- md_store_group_t group, const char *name)
+ md_store_group_t group, const char *name,
+ apr_time_t min_delay)
{
md_job_t *job = apr_pcalloc(p, sizeof(*job));
job->group = group;
@@ -294,6 +295,7 @@ md_job_t *md_job_make(apr_pool_t *p, md_
job->store = store;
job->p = p;
job->max_log = 128;
+ job->min_delay = min_delay;
return job;
}

@@ -588,7 +590,7 @@ apr_time_t md_job_delay_on_errors(md_job
}
else if (err_count > 0) {
/* back off duration, depending on the errors we encounter in a row */
- delay = apr_time_from_sec(5 << (err_count - 1));
+ delay = job->min_delay << (err_count - 1);
if (delay > max_delay) {
delay = max_delay;
}

Modified: httpd/httpd/branches/2.4.x/modules/md/md_status.h
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md_status.h?rev=1900950&r1=1900949&r2=1900950&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md_status.h (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md_status.h Mon May 16 11:36:20 2022
@@ -68,6 +68,7 @@ struct md_job_t {
apr_size_t max_log; /* max number of log entries, new ones replace oldest */
int dirty;
struct md_result_t *observing;
+ apr_time_t min_delay; /* smallest delay a repeated attempt should have */
};

/**
@@ -75,7 +76,8 @@ struct md_job_t {
* Job load/save will work using the name.
*/
md_job_t *md_job_make(apr_pool_t *p, md_store_t *store,
- md_store_group_t group, const char *name);
+ md_store_group_t group, const char *name,
+ apr_time_t min_delay);

void md_job_set_group(md_job_t *job, md_store_group_t group);


Modified: httpd/httpd/branches/2.4.x/modules/md/md_tailscale.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md_tailscale.c?rev=1900950&r1=1900949&r2=1900950&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md_tailscale.c (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md_tailscale.c Mon May 16 11:36:20 2022
@@ -56,7 +56,8 @@ static apr_status_t ts_init(md_proto_dri
ts_ctx->driver = d;
ts_ctx->chain = apr_array_make(d->p, 5, sizeof(md_cert_t *));

- ca_url = d->md->ca_url;
+ 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;
}
@@ -254,7 +255,7 @@ static apr_status_t ts_renew(md_proto_dr
ts_ctx->md = NULL;
}

- if (!ts_ctx->md || strcmp(ts_ctx->md->ca_url, d->md->ca_url)) {
+ 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);
@@ -361,8 +362,9 @@ leave:
static apr_status_t ts_complete_md(md_t *md, apr_pool_t *p)
{
(void)p;
- if (!md->ca_url) {
- md->ca_url = MD_TAILSCALE_DEF_URL;
+ 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;
}

Modified: httpd/httpd/branches/2.4.x/modules/md/md_version.h
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md_version.h?rev=1900950&r1=1900949&r2=1900950&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md_version.h (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md_version.h Mon May 16 11:36:20 2022
@@ -27,7 +27,7 @@
* @macro
* Version number of the md module as c string
*/
-#define MOD_MD_VERSION "2.4.15"
+#define MOD_MD_VERSION "2.4.16"

/**
* @macro
@@ -35,7 +35,7 @@
* 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 0x02040f
+#define MOD_MD_VERSION_NUM 0x020410

#define MD_ACME_DEF_URL "https://acme-v02.api.letsencrypt.org/directory"
#define MD_TAILSCALE_DEF_URL "file://localhost/var/run/tailscale/tailscaled.sock"

Modified: httpd/httpd/branches/2.4.x/modules/md/mod_md.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/mod_md.c?rev=1900950&r1=1900949&r2=1900950&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/mod_md.c (original)
+++ httpd/httpd/branches/2.4.x/modules/md/mod_md.c Mon May 16 11:36:20 2022
@@ -313,8 +313,8 @@ static void merge_srv_config(md_t *md, m
md->sc = base_sc;
}

- if (!md->ca_url) {
- md->ca_url = md_config_gets(md->sc, MD_CONFIG_CA_URL);
+ if (!md->ca_urls && md->sc->ca_urls) {
+ md->ca_urls = apr_array_copy(p, md->sc->ca_urls);
}
if (!md->ca_proto) {
md->ca_proto = md_config_gets(md->sc, MD_CONFIG_CA_PROTO);
@@ -705,7 +705,7 @@ static apr_status_t merge_mds_with_conf(
ap_log_error(APLOG_MARK, log_level, 0, base_server, APLOGNO(10039)
"Completed MD[.%s, CA=%s, Proto=%s, Agreement=%s, renew-mode=%d "
"renew_window=%s, warn_window=%s",
- md->name, md->ca_url, md->ca_proto, md->ca_agreement, md->renew_mode,
+ md->name, md->ca_effective, md->ca_proto, md->ca_agreement, md->renew_mode,
md->renew_window? md_timeslice_format(md->renew_window, p) : "unset",
md->warn_window? md_timeslice_format(md->warn_window, p) : "unset");
}
@@ -886,16 +886,21 @@ static apr_status_t md_post_config_befor

md_event_init(p);
md_event_subscribe(on_event, mc);
-
- if (APR_SUCCESS != (rv = setup_store(&store, mc, p, s))
- || APR_SUCCESS != (rv = md_reg_create(&mc->reg, p, store, mc->proxy_url, mc->ca_certs))) {
+
+ rv = setup_store(&store, mc, p, s);
+ if (APR_SUCCESS != rv) goto leave;
+
+ rv = md_reg_create(&mc->reg, p, store, mc->proxy_url, mc->ca_certs,
+ mc->min_delay, mc->retry_failover);
+ if (APR_SUCCESS != rv) {
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10072) "setup md registry");
goto leave;
}

/* renew on 30% remaining /*/
rv = md_ocsp_reg_make(&mc->ocsp, p, store, mc->ocsp_renew_window,
- AP_SERVER_BASEVERSION, mc->proxy_url);
+ AP_SERVER_BASEVERSION, mc->proxy_url,
+ mc->min_delay);
if (APR_SUCCESS != rv) {
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10196) "setup ocsp registry");
goto leave;

Modified: httpd/httpd/branches/2.4.x/modules/md/mod_md_config.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/mod_md_config.c?rev=1900950&r1=1900949&r2=1900950&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/mod_md_config.c (original)
+++ httpd/httpd/branches/2.4.x/modules/md/mod_md_config.c Mon May 16 11:36:20 2022
@@ -84,6 +84,8 @@ static md_mod_conf_t defmc = {
"crt.sh", /* default cert checker site name */
"https://crt.sh?q=", /* default cert checker site url */
NULL, /* CA cert file to use */
+ apr_time_from_sec(5), /* minimum delay for retries */
+ 13, /* retry_failover after 14 errors, with 5s delay ~ half a day */
};

static md_timeslice_t def_renew_window = {
@@ -107,7 +109,7 @@ static md_srv_conf_t defconf = {
NULL, /* pkey spec */
&def_renew_window, /* renew window */
&def_warn_window, /* warn window */
- NULL, /* ca url */
+ NULL, /* ca urls */
NULL, /* ca contact (email) */
MD_PROTO_ACME, /* ca protocol */
NULL, /* ca agreemnent */
@@ -161,7 +163,7 @@ static void srv_conf_props_clear(md_srv_
sc->pks = NULL;
sc->renew_window = NULL;
sc->warn_window = NULL;
- sc->ca_url = NULL;
+ sc->ca_urls = NULL;
sc->ca_contact = NULL;
sc->ca_proto = NULL;
sc->ca_agreement = NULL;
@@ -181,7 +183,7 @@ static void srv_conf_props_copy(md_srv_c
to->pks = from->pks;
to->warn_window = from->warn_window;
to->renew_window = from->renew_window;
- to->ca_url = from->ca_url;
+ to->ca_urls = from->ca_urls;
to->ca_contact = from->ca_contact;
to->ca_proto = from->ca_proto;
to->ca_agreement = from->ca_agreement;
@@ -201,7 +203,7 @@ static void srv_conf_props_apply(md_t *m
if (from->pks) md->pks = md_pkeys_spec_clone(p, from->pks);
if (from->renew_window) md->renew_window = from->renew_window;
if (from->warn_window) md->warn_window = from->warn_window;
- if (from->ca_url) md->ca_url = from->ca_url;
+ if (from->ca_urls) md->ca_urls = apr_array_copy(p, from->ca_urls);
if (from->ca_proto) md->ca_proto = from->ca_proto;
if (from->ca_agreement) md->ca_agreement = from->ca_agreement;
if (from->ca_contact) {
@@ -247,7 +249,8 @@ static void *md_config_merge(apr_pool_t
nsc->renew_window = add->renew_window? add->renew_window : base->renew_window;
nsc->warn_window = add->warn_window? add->warn_window : base->warn_window;

- nsc->ca_url = add->ca_url? add->ca_url : base->ca_url;
+ nsc->ca_urls = add->ca_urls? apr_array_copy(pool, add->ca_urls)
+ : (base->ca_urls? apr_array_copy(pool, base->ca_urls) : NULL);
nsc->ca_contact = add->ca_contact? add->ca_contact : base->ca_contact;
nsc->ca_proto = add->ca_proto? add->ca_proto : base->ca_proto;
nsc->ca_agreement = add->ca_agreement? add->ca_agreement : base->ca_agreement;
@@ -475,19 +478,29 @@ static const char *md_config_set_names(c
return NULL;
}

-static const char *md_config_set_ca(cmd_parms *cmd, void *dc, const char *value)
+static const char *md_config_set_ca(cmd_parms *cmd, void *dc,
+ int argc, char *const argv[])
{
md_srv_conf_t *sc = md_config_get(cmd->server);
const char *err, *url;
+ int i;

(void)dc;
if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) {
return err;
}
- if (APR_SUCCESS != md_get_ca_url_from_name(&url, cmd->pool, value)) {
- return url;
+ if (!sc->ca_urls) {
+ sc->ca_urls = apr_array_make(cmd->pool, 3, sizeof(const char *));
+ }
+ else {
+ apr_array_clear(sc->ca_urls);
+ }
+ for (i = 0; i < argc; ++i) {
+ if (APR_SUCCESS != md_get_ca_url_from_name(&url, cmd->pool, argv[i])) {
+ return url;
+ }
+ APR_ARRAY_PUSH(sc->ca_urls, const char *) = url;
}
- sc->ca_url = url;
return NULL;
}

@@ -603,6 +616,37 @@ static const char *md_config_set_base_se
return set_on_off(&config->mc->manage_base_server, value, cmd->pool);
}

+static const char *md_config_set_min_delay(cmd_parms *cmd, void *dc, const char *value)
+{
+ md_srv_conf_t *config = md_config_get(cmd->server);
+ const char *err = md_conf_check_location(cmd, MD_LOC_NOT_MD);
+ apr_time_t delay;
+
+ (void)dc;
+ if (err) return err;
+ if (md_duration_parse(&delay, value, "s") != APR_SUCCESS) {
+ return "unrecognized duration format";
+ }
+ config->mc->min_delay = delay;
+ return NULL;
+}
+
+static const char *md_config_set_retry_failover(cmd_parms *cmd, void *dc, const char *value)
+{
+ md_srv_conf_t *config = md_config_get(cmd->server);
+ const char *err = md_conf_check_location(cmd, MD_LOC_NOT_MD);
+ int retry_failover;
+
+ (void)dc;
+ if (err) return err;
+ retry_failover = atoi(value);
+ if (retry_failover <= 0) {
+ return "invalid argument, must be a number > 0";
+ }
+ config->mc->retry_failover = retry_failover;
+ return NULL;
+}
+
static const char *md_config_set_require_https(cmd_parms *cmd, void *dc, const char *value)
{
md_srv_conf_t *config = md_config_get(cmd->server);
@@ -1090,8 +1134,8 @@ leave:
}

const command_rec md_cmds[] = {
- AP_INIT_TAKE1("MDCertificateAuthority", md_config_set_ca, NULL, RSRC_CONF,
- "URL or known name of CA issuing the certificates"),
+ AP_INIT_TAKE_ARGV("MDCertificateAuthority", md_config_set_ca, NULL, RSRC_CONF,
+ "URL(s) or known name(s) of CA issuing the certificates"),
AP_INIT_TAKE1("MDCertificateAgreement", md_config_set_agreement, NULL, RSRC_CONF,
"either 'accepted' or the URL of CA Terms-of-Service agreement you accept"),
AP_INIT_TAKE_ARGV("MDCAChallenges", md_config_set_cha_tyes, NULL, RSRC_CONF,
@@ -1167,6 +1211,10 @@ const command_rec md_cmds[] = {
"Set the CA file to use for connections"),
AP_INIT_TAKE12("MDExternalAccountBinding", md_config_set_eab, NULL, RSRC_CONF,
"Set the external account binding keyid and hmac values to use at CA"),
+ AP_INIT_TAKE1("MDRetryDelay", md_config_set_min_delay, NULL, RSRC_CONF,
+ "Time length for first retry, doubled on every consecutive error."),
+ AP_INIT_TAKE1("MDRetryFailover", md_config_set_retry_failover, NULL, RSRC_CONF,
+ "The number of errors before a failover to another CA is triggered."),

AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL)
};
@@ -1226,8 +1274,6 @@ md_srv_conf_t *md_config_cget(conn_rec *
const char *md_config_gets(const md_srv_conf_t *sc, md_config_var_t var)
{
switch (var) {
- case MD_CONFIG_CA_URL:
- return sc->ca_url? sc->ca_url : defconf.ca_url;
case MD_CONFIG_CA_CONTACT:
return sc->ca_contact? sc->ca_contact : defconf.ca_contact;
case MD_CONFIG_CA_PROTO:

Modified: httpd/httpd/branches/2.4.x/modules/md/mod_md_config.h
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/mod_md_config.h?rev=1900950&r1=1900949&r2=1900950&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/mod_md_config.h (original)
+++ httpd/httpd/branches/2.4.x/modules/md/mod_md_config.h Mon May 16 11:36:20 2022
@@ -24,7 +24,6 @@ struct md_ocsp_reg_t;
struct md_pkeys_spec_t;

typedef enum {
- MD_CONFIG_CA_URL,
MD_CONFIG_CA_CONTACT,
MD_CONFIG_CA_PROTO,
MD_CONFIG_BASE_DIR,
@@ -71,6 +70,8 @@ struct md_mod_conf_t {
const char *cert_check_name; /* name of the linked certificate check site */
const char *cert_check_url; /* url "template for" checking a certificate */
const char *ca_certs; /* root certificates to use for connections */
+ apr_time_t min_delay; /* minimum delay for retries */
+ int retry_failover; /* number of errors to trigger CA failover */
};

typedef struct md_srv_conf_t {
@@ -86,7 +87,7 @@ typedef struct md_srv_conf_t {
md_timeslice_t *renew_window; /* time before expiration that starts renewal */
md_timeslice_t *warn_window; /* time before expiration that warning are sent out */

- const char *ca_url; /* url of CA certificate service */
+ struct apr_array_header_t *ca_urls; /* urls of CAs */
const char *ca_contact; /* contact email registered to account */
const char *ca_proto; /* protocol used vs CA (e.g. ACME) */
const char *ca_agreement; /* accepted agreement uri between CA and user */

Modified: httpd/httpd/branches/2.4.x/modules/md/mod_md_drive.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/mod_md_drive.c?rev=1900950&r1=1900949&r2=1900950&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/mod_md_drive.c (original)
+++ httpd/httpd/branches/2.4.x/modules/md/mod_md_drive.c Mon May 16 11:36:20 2022
@@ -125,7 +125,7 @@ static void process_drive_job(md_renew_c
}

md_job_start_run(job, result, md_reg_store_get(dctx->mc->reg));
- md_reg_renew(dctx->mc->reg, md, dctx->mc->env, 0, result, ptemp);
+ md_reg_renew(dctx->mc->reg, md, dctx->mc->env, 0, job->error_runs, result, ptemp);
md_job_end_run(job, result);

if (APR_SUCCESS == result->status) {

Modified: httpd/httpd/branches/2.4.x/modules/md/mod_md_status.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/mod_md_status.c?rev=1900950&r1=1900949&r2=1900950&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/mod_md_status.c (original)
+++ httpd/httpd/branches/2.4.x/modules/md/mod_md_status.c Mon May 16 11:36:20 2022
@@ -349,32 +349,65 @@ static void si_val_cert_valid_time(statu
if (jcert) si_val_valid_time(ctx, jcert, &sub);
}

-static void si_val_ca_url(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+static void val_url_print(status_ctx *ctx, const status_info *info,
+ const char*url, const char *proto, int i)
+{
+ const char *s;
+
+ if (proto && !strcmp(proto, "tailscale")) {
+ s = "tailscale";
+ }
+ else if (url) {
+ s = md_get_ca_name_from_url(ctx->p, url);
+ }
+ else {
+ return;
+ }
+ if (HTML_STATUS(ctx)) {
+ apr_brigade_printf(ctx->bb, NULL, NULL, "%s<a href='%s'>%s</a>",
+ i? " " : "",
+ ap_escape_html2(ctx->p, url, 1),
+ ap_escape_html2(ctx->p, s, 1));
+ }
+ else if (i == 0) {
+ apr_brigade_printf(ctx->bb, NULL, NULL, "%s%sName: %s\n",
+ ctx->prefix, info->label, s);
+ apr_brigade_printf(ctx->bb, NULL, NULL, "%s%sURL: %s\n",
+ ctx->prefix, info->label, url);
+ }
+ else {
+ apr_brigade_printf(ctx->bb, NULL, NULL, "%s%sName%d: %s\n",
+ ctx->prefix, info->label, i, s);
+ apr_brigade_printf(ctx->bb, NULL, NULL, "%s%sURL%d: %s\n",
+ ctx->prefix, info->label, i, url);
+ }
+}
+
+static void si_val_ca_urls(status_ctx *ctx, md_json_t *mdj, const status_info *info)
{
md_json_t *jcert;
+ const char *proto, *url;
+ apr_array_header_t *urls;
+ int i;

jcert = md_json_getj(mdj, info->key, NULL);
- if (jcert) {
- const char *proto, *s, *url;
+ if (!jcert) {
+ return;
+ }

- proto = md_json_gets(jcert, MD_KEY_PROTO, NULL);
- s = url = md_json_gets(jcert, MD_KEY_URL, NULL);
- if (proto && !strcmp(proto, "tailscale")) {
- s = "tailscale";
- }
- else if (url) {
- s = md_get_ca_name_from_url(ctx->p, url);
- }
- if (HTML_STATUS(ctx)) {
- apr_brigade_printf(ctx->bb, NULL, NULL, "<a href='%s'>%s</a>",
- ap_escape_html2(ctx->p, url, 1),
- ap_escape_html2(ctx->p, s, 1));
- }
- else {
- apr_brigade_printf(ctx->bb, NULL, NULL, "%s%sName: %s\n",
- ctx->prefix, info->label, s);
- apr_brigade_printf(ctx->bb, NULL, NULL, "%s%sURL: %s\n",
- ctx->prefix, info->label, url);
+ proto = md_json_gets(jcert, MD_KEY_PROTO, NULL);
+ url = md_json_gets(jcert, MD_KEY_URL, NULL);
+ if (url) {
+ /* print the effective CA url used, if set */
+ val_url_print(ctx, info, url, proto, 0);
+ }
+ else {
+ /* print the available CA urls configured */
+ urls = apr_array_make(ctx->p, 3, sizeof(const char*));
+ md_json_getsa(urls, jcert, MD_KEY_URLS, NULL);
+ for (i = 0; i < urls->nelts; ++i) {
+ url = APR_ARRAY_IDX(urls, i, const char*);
+ val_url_print(ctx, info, url, proto, i);
}
}
}
@@ -673,7 +706,7 @@ static const status_info status_infos[]
{ "Names", MD_KEY_DOMAINS, si_val_names },
{ "Status", MD_KEY_STATE, si_val_status },
{ "Valid", MD_KEY_CERT, si_val_cert_valid_time },
- { "CA", MD_KEY_CA, si_val_ca_url },
+ { "CA", MD_KEY_CA, si_val_ca_urls },
{ "Stapling", MD_KEY_STAPLING, si_val_stapling },
{ "CheckAt", MD_KEY_SHA256_FINGERPRINT, si_val_remote_check },
{ "Activity", MD_KEY_NOTIFIED, si_val_activity },

Modified: httpd/httpd/branches/2.4.x/test/modules/md/md_conf.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/md_conf.py?rev=1900950&r1=1900949&r2=1900950&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/md_conf.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/md/md_conf.py Mon May 16 11:36:20 2022
@@ -13,7 +13,9 @@ class MDConf(HttpdConf):
admin = f"admin@{env.http_tld}"
if len(admin.strip()):
self.add_admin(admin)
-
+ self.add([.
+ "MDRetryDelay 1s", # speed up testing a little
+ ])
if local_ca:
self.add([
f"MDCertificateAuthority {env.acme_url}",
@@ -23,7 +25,7 @@ class MDConf(HttpdConf):
])
if std_ports:
self.add(f"MDPortMap 80:{env.http_port} 443:{env.https_port}")
- if env.ssl_module == "tls":
+ if env.ssl_module == "mod_tls":
self.add(f"TLSListen {env.https_port}")
self.add([
"<Location /server-status>",

Modified: httpd/httpd/branches/2.4.x/test/modules/md/md_env.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/md_env.py?rev=1900950&r1=1900949&r2=1900950&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/md_env.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/md/md_env.py Mon May 16 11:36:20 2022
@@ -313,7 +313,8 @@ class MDTestEnv(HttpdTestEnv):
if state >= 0:
assert md['state'] == state
if ca:
- assert md['ca']['url'] == ca
+ assert len(md['ca']['urls']) == 1
+ assert md['ca']['urls'][0] == ca
if protocol:
assert md['ca']['proto'] == protocol
if agreement:
@@ -343,6 +344,7 @@ class MDTestEnv(HttpdTestEnv):
assert False, f"pkey missing: {pkey_file}: {r.stdout}"
if not os.path.isfile(cert_file):
assert False, f"cert missing: {cert_file}: {r.stdout}"
+ return md

def check_md_credentials(self, domain):
if isinstance(domain, list):

Modified: httpd/httpd/branches/2.4.x/test/modules/md/test_001_store.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/test_001_store.py?rev=1900950&r1=1900949&r2=1900950&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/test_001_store.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/md/test_001_store.py Mon May 16 11:36:20 2022
@@ -39,7 +39,7 @@ class TestStore:
"domains": [dns],
"contacts": [],
"ca": {
- "url": env.acme_url,
+ "urls": [env.acme_url],
"proto": "ACME"
},
"state": 0
@@ -55,7 +55,7 @@ class TestStore:
"domains": dns,
"contacts": [],
"ca": {
- "url": env.acme_url,
+ "urls": [env.acme_url],
"proto": "ACME"
},
"state": 0
@@ -76,7 +76,7 @@ class TestStore:
"domains": dns2,
"contacts": [],
"ca": {
- "url": env.acme_url,
+ "urls": [env.acme_url],
"proto": "ACME"
},
"state": 0
@@ -129,7 +129,7 @@ class TestStore:
"domains": domains[i],
"contacts": [],
"ca": {
- "url": env.acme_url,
+ "urls": [env.acme_url],
"proto": "ACME"
},
"state": 0
@@ -186,10 +186,10 @@ class TestStore:
def test_md_001_402(self, env: MDTestEnv):
dns = "test000-402.com"
args = ["store", "add", dns]
- assert env.a2md(args).json['output'][0]['ca']['url'] == env.acme_url
+ assert env.a2md(args).json['output'][0]['ca']['urls'][0] == env.acme_url
nurl = "https://foo.com/"
args = [env.a2md_bin, "-a", nurl, "-d", env.store_dir, "-j", "store", "update", dns]
- assert env.run(args).json['output'][0]['ca']['url'] == nurl
+ assert env.run(args).json['output'][0]['ca']['urls'][0] == nurl

# test case: update nonexisting managed domain
def test_md_001_403(self, env: MDTestEnv):

Modified: httpd/httpd/branches/2.4.x/test/modules/md/test_100_reg_add.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/test_100_reg_add.py?rev=1900950&r1=1900949&r2=1900950&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/test_100_reg_add.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/md/test_100_reg_add.py Mon May 16 11:36:20 2022
@@ -23,7 +23,7 @@ class TestRegAdd:
"domains": [dns],
"contacts": [],
"ca": {
- "url": env.acme_url,
+ "urls": [env.acme_url],
"proto": "ACME"
},
"state": env.MD_S_INCOMPLETE
@@ -39,7 +39,7 @@ class TestRegAdd:
"domains": dns,
"contacts": [],
"ca": {
- "url": env.acme_url,
+ "urls": [env.acme_url],
"proto": "ACME"
},
"state": env.MD_S_INCOMPLETE
@@ -60,7 +60,7 @@ class TestRegAdd:
"domains": dns2,
"contacts": [],
"ca": {
- "url": env.acme_url,
+ "urls": [env.acme_url],
"proto": "ACME"
},
"state": env.MD_S_INCOMPLETE

Modified: httpd/httpd/branches/2.4.x/test/modules/md/test_110_reg_update.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/test_110_reg_update.py?rev=1900950&r1=1900949&r2=1900950&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/test_110_reg_update.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/md/test_110_reg_update.py Mon May 16 11:36:20 2022
@@ -37,7 +37,7 @@ class TestRegUpdate:
"domains": dns,
"contacts": [],
"ca": {
- "url": env.acme_url,
+ "urls": [env.acme_url],
"proto": "ACME"
},
"state": env.MD_S_INCOMPLETE
@@ -104,7 +104,7 @@ class TestRegUpdate:
"domains": [self.NAME1, "www.greenbytes2.de", "mail.greenbytes2.de"],
"contacts": [],
"ca": {
- "url": url,
+ "urls": [url],
"proto": "ACME"
},
"state": env.MD_S_INCOMPLETE
@@ -121,7 +121,7 @@ class TestRegUpdate:
def test_md_110_102(self, env):
md = env.a2md(["update", self.NAME1, "ca", env.acme_url, "FOO"]).json['output'][0]
env.check_json_contains(md['ca'], {
- "url": env.acme_url,
+ "urls": [env.acme_url],
"proto": "FOO"
})
assert md['state'] == 1
@@ -137,7 +137,7 @@ class TestRegUpdate:
"contacts": [],
"ca": {
"account": acc_id,
- "url": env.acme_url,
+ "urls": [env.acme_url],
"proto": "ACME"
},
"state": env.MD_S_INCOMPLETE
@@ -148,7 +148,7 @@ class TestRegUpdate:
assert env.a2md(["update", self.NAME1, "account", "test.account.id"]).exit_code == 0
md = env.a2md(["update", self.NAME1, "account"]).json['output'][0]
env.check_json_contains(md['ca'], {
- "url": env.acme_url,
+ "urls": [env.acme_url],
"proto": "ACME"
})
assert md['state'] == 1
@@ -159,7 +159,7 @@ class TestRegUpdate:
md = env.a2md(["update", self.NAME1, "account", "foo.test.com"]).json['output'][0]
env.check_json_contains(md['ca'], {
"account": "foo.test.com",
- "url": env.acme_url,
+ "urls": [env.acme_url],
"proto": "ACME"
})
assert md['state'] == 1
@@ -170,7 +170,7 @@ class TestRegUpdate:
"test2.account.id"]).json['output'][0]
env.check_json_contains(md['ca'], {
"account": "test.account.id",
- "url": env.acme_url,
+ "urls": [env.acme_url],
"proto": "ACME"
})
assert md['state'] == 1
@@ -185,7 +185,7 @@ class TestRegUpdate:
"domains": [self.NAME1, "www.greenbytes2.de", "mail.greenbytes2.de"],
"contacts": ["mailto:" + mail],
"ca": {
- "url": env.acme_url,
+ "urls": [env.acme_url],
"proto": "ACME"
},
"state": env.MD_S_INCOMPLETE
@@ -237,7 +237,7 @@ class TestRegUpdate:
"domains": [self.NAME1, "www.greenbytes2.de", "mail.greenbytes2.de"],
"contacts": [],
"ca": {
- "url": env.acme_url,
+ "urls": [env.acme_url],
"proto": "ACME",
"agreement": env.acme_tos
},
@@ -249,7 +249,7 @@ class TestRegUpdate:
assert env.a2md(["update", self.NAME1, "agreement", env.acme_tos]).exit_code == 0
md = env.a2md(["update", self.NAME1, "agreement"]).json['output'][0]
env.check_json_contains(md['ca'], {
- "url": env.acme_url,
+ "urls": [env.acme_url],
"proto": "ACME"
})
assert md['state'] == 1
@@ -259,7 +259,7 @@ class TestRegUpdate:
md = env.a2md(["update", self.NAME1, "agreement",
env.acme_tos, "http://invalid.tos/"]).json['output'][0]
env.check_json_contains(md['ca'], {
- "url": env.acme_url,
+ "urls": [env.acme_url],
"proto": "ACME",
"agreement": env.acme_tos
})

Modified: httpd/httpd/branches/2.4.x/test/modules/md/test_120_reg_list.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/test_120_reg_list.py?rev=1900950&r1=1900949&r2=1900950&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/test_120_reg_list.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/md/test_120_reg_list.py Mon May 16 11:36:20 2022
@@ -39,7 +39,7 @@ class TestRegAdd:
"domains": domains[i],
"contacts": [],
"ca": {
- "url": env.acme_url,
+ "urls": [env.acme_url],
"proto": "ACME"
},
"state": env.MD_S_INCOMPLETE

Modified: httpd/httpd/branches/2.4.x/test/modules/md/test_300_conf_validate.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/test_300_conf_validate.py?rev=1900950&r1=1900949&r2=1900950&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/test_300_conf_validate.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/md/test_300_conf_validate.py Mon May 16 11:36:20 2022
@@ -340,5 +340,51 @@ class TestConf:
conf.install()
assert env.apache_restart() == 0, "Server did not accepted CA '{}'".format(ca)
md = env.get_md_status(domain)
- assert md['ca']['url'] == url
+ assert md['ca']['urls'][0] == url, f"CA url '{url}' not set in {md}"

+ # vhost on another address, see #278
+ def test_md_300_026(self, env):
+ assert env.apache_stop() == 0
+ conf = MDConf(env)
+ domain = f"t300_026.{env.http_tld}"
+ conf.add(f"""
+ MDomain {domain}
+ """)
+ conf.add_vhost(port=env.http_port, domains=[domain], with_ssl=False)
+ conf.add(f"""
+ <VirtualHost 10.0.0.1:{env.https_port}>
+ ServerName {domain}
+ ServerAlias xxx.{env.http_tld}
+ SSLEngine on
+ </VirtualHost>
+ <VirtualHost 10.0.0.1:12345>
+ ServerName {domain}
+ SSLEngine on
+ </VirtualHost>
+ """)
+ conf.install()
+ assert env.apache_restart() == 0
+
+ # test case: configure more than 1 CA
+ @pytest.mark.parametrize("cas, should_work", [
+ (["https://acme-v02.api.letsencrypt.org/directory"], True),
+ (["https://acme-v02.api.letsencrypt.org/directory", "buypass"], True),
+ (["x", "buypass"], False),
+ (["letsencrypt", "abc"], False),
+ (["letsencrypt", "buypass"], True),
+ ])
+ def test_md_300_027(self, env, cas, should_work):
+ domain = f"test1.{env.http_tld}"
+ conf = MDConf(env, text=f"""
+ MDCertificateAuthority {' '.join(cas)}
+ MDRenewMode manual
+ """)
+ conf.add_md([domain])
+ conf.install()
+ rv = env.apache_restart()
+ if should_work:
+ assert rv == 0, "Server did not accepted CAs '{}'".format(cas)
+ md = env.get_md_status(domain)
+ assert len(md['ca']['urls']) == len(cas)
+ else:
+ assert rv != 0, "Server should not have accepted CAs '{}'".format(cas)

Modified: httpd/httpd/branches/2.4.x/test/modules/md/test_702_auto.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/md/test_702_auto.py?rev=1900950&r1=1900949&r2=1900950&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/md/test_702_auto.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/md/test_702_auto.py Mon May 16 11:36:20 2022
@@ -1,4 +1,6 @@
import os
+import time
+
import pytest

from pyhttpd.conf import HttpdConf
@@ -131,7 +133,8 @@ class TestAutov2:
assert env.apache_restart() == 0
env.check_md(domains)
assert env.await_completion([domain])
- env.check_md_complete(domain)
+ md = env.check_md_complete(domain)
+ assert md['ca']['url'], f"URL of CA used not set in md: {md}"
#
# check: SSL is running OK
cert_a = env.get_cert(name_a)