summaryrefslogtreecommitdiff
path: root/modules/md
diff options
context:
space:
mode:
authorStefan Eissing <icing@apache.org>2022-05-13 11:03:51 +0000
committerStefan Eissing <icing@apache.org>2022-05-13 11:03:51 +0000
commite6e83f275f4f7e66023a0dec83dfe2ca147bb536 (patch)
tree06894ac4fa8570dc4c845bcbb99fd9783c1c3e6f /modules/md
parentcda87408aeafb96af7f7972ff9157a9e656e9b7f (diff)
downloadhttpd-e6e83f275f4f7e66023a0dec83dfe2ca147bb536.tar.gz
*) 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. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1900852 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'modules/md')
-rw-r--r--modules/md/md.h4
-rw-r--r--modules/md/md_acme_acct.c4
-rw-r--r--modules/md/md_acme_drive.c95
-rw-r--r--modules/md/md_core.c21
-rw-r--r--modules/md/md_ocsp.c7
-rw-r--r--modules/md/md_ocsp.h3
-rw-r--r--modules/md/md_reg.c49
-rw-r--r--modules/md/md_reg.h15
-rw-r--r--modules/md/md_status.c6
-rw-r--r--modules/md/md_status.h4
-rw-r--r--modules/md/md_tailscale.c10
-rw-r--r--modules/md/md_version.h4
-rw-r--r--modules/md/mod_md.c19
-rw-r--r--modules/md/mod_md_config.c72
-rw-r--r--modules/md/mod_md_config.h5
-rw-r--r--modules/md/mod_md_drive.c2
-rw-r--r--modules/md/mod_md_status.c74
17 files changed, 288 insertions, 106 deletions
diff --git a/modules/md/md.h b/modules/md/md.h
index 5a3aaecdac..af695f1458 100644
--- a/modules/md/md.h
+++ b/modules/md/md.h
@@ -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"
diff --git a/modules/md/md_acme_acct.c b/modules/md/md_acme_acct.c
index 94dd83190d..f3e043e87c 100644
--- a/modules/md/md_acme_acct.c
+++ b/modules/md/md_acme_acct.c
@@ -243,7 +243,7 @@ int md_acme_acct_matches_url(md_acme_acct_t *acct, const char *url)
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 char *name, const char *aspect,
&& (!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;
}
diff --git a/modules/md/md_acme_drive.c b/modules/md/md_acme_drive.c
index bc0f17f271..abe7d644e6 100644
--- a/modules/md/md_acme_drive.c
+++ b/modules/md/md_acme_drive.c
@@ -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_driver_t *d, md_result_t *result)
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_driver_t *d, md_result_t *result)
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_driver_t *d, md_result_t *result)
}
/* 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_driver_t *d, md_result_t *result)
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_proto_driver_t *d, md_store_group_t load_gro
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_proto_driver_t *d, md_store_group_t load_gro
}
}
- 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(md_proto_driver_t *d,
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;
}
diff --git a/modules/md/md_core.c b/modules/md/md_core.c
index f82f950503..8c7c453625 100644
--- a/modules/md/md_core.c
+++ b/modules/md/md_core.c
@@ -241,8 +241,11 @@ md_t *md_clone(apr_pool_t *p, const md_t *src)
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, apr_pool_t *p)
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_pool_t *p)
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);
diff --git a/modules/md/md_ocsp.c b/modules/md/md_ocsp.c
index 67c6e12d80..8cbf05b3e1 100644
--- a/modules/md/md_ocsp.c
+++ b/modules/md/md_ocsp.c
@@ -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(void *data)
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_reg_t **preg, apr_pool_t *p, md_store_t *s
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 **pjson, md_ocsp_reg_t *reg, apr_pool_t *p
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);
}
diff --git a/modules/md/md_ocsp.h b/modules/md/md_ocsp.h
index d6ee0f1d7d..c91dc54906 100644
--- a/modules/md/md_ocsp.h
+++ b/modules/md/md_ocsp.h
@@ -38,7 +38,8 @@ typedef struct md_ocsp_reg_t md_ocsp_reg_t;
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);
diff --git a/modules/md/md_reg.c b/modules/md/md_reg.c
index 0c59aeb737..21374fc1af 100644
--- a/modules/md/md_reg.c
+++ b/modules/md/md_reg.c
@@ -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 *reg, apr_pool_t *p)
}
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 **preg, apr_pool_t *p, struct md_store_t *st
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_t *reg, apr_pool_t *p, const md_t *md, i
}
}
- 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 *baton, apr_pool_t *p, apr_pool_t *ptemp, v
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 *reg, md_t *md, apr_pool_t *p, apr_pool
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 *reg, const md_t *md, struct apr_table_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 *baton, apr_pool_t *p, apr_pool_t *ptemp, va_
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 *baton, apr_pool_t *p, apr_pool_t *ptemp, va_
}
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_reg_t *reg, md_timeslice_t *warn_window)
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);
}
diff --git a/modules/md/md_reg.h b/modules/md/md_reg.h
index aa626c9276..ccaf10253a 100644
--- a/modules/md/md_reg.h
+++ b/modules/md/md_reg.h
@@ -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 *reg, const md_t *md, struct apr_table_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);
/**
diff --git a/modules/md/md_status.c b/modules/md/md_status.c
index 32efc19a67..936c65349f 100644
--- a/modules/md/md_status.c
+++ b/modules/md/md_status.c
@@ -286,7 +286,8 @@ apr_status_t md_status_get_json(md_json_t **pjson, apr_array_header_t *mds,
/* 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_store_t *store,
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_t *job, int err_count, const char *last
}
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;
}
diff --git a/modules/md/md_status.h b/modules/md/md_status.h
index cd358b0e8e..f4d09bd90f 100644
--- a/modules/md/md_status.h
+++ b/modules/md/md_status.h
@@ -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);
diff --git a/modules/md/md_tailscale.c b/modules/md/md_tailscale.c
index dd3b1458ad..c8d2bad64c 100644
--- a/modules/md/md_tailscale.c
+++ b/modules/md/md_tailscale.c
@@ -56,7 +56,8 @@ static apr_status_t ts_init(md_proto_driver_t *d, md_result_t *result)
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_driver_t *d, md_result_t *result)
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;
}
diff --git a/modules/md/md_version.h b/modules/md/md_version.h
index d634538e1a..4b8aef13d6 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.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"
diff --git a/modules/md/mod_md.c b/modules/md/mod_md.c
index 34f43311e3..d5237f4699 100644
--- a/modules/md/mod_md.c
+++ b/modules/md/mod_md.c
@@ -313,8 +313,8 @@ static void merge_srv_config(md_t *md, md_srv_conf_t *base_sc, apr_pool_t *p)
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(md_mod_conf_t *mc, apr_pool_t *p,
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_before_ssl(apr_pool_t *p, apr_pool_t *plog,
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;
diff --git a/modules/md/mod_md_config.c b/modules/md/mod_md_config.c
index 82c7191768..f096ad238f 100644
--- a/modules/md/mod_md_config.c
+++ b/modules/md/mod_md_config.c
@@ -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_conf_t *sc)
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_conf_t *to, const md_srv_conf_t *from)
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 *md, const md_srv_conf_t *from, apr_pool_t
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 *pool, void *basev, void *addv)
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(cmd_parms *cmd, void *dc,
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_server(cmd_parms *cmd, void *dc, const cha
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 *c)
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:
diff --git a/modules/md/mod_md_config.h b/modules/md/mod_md_config.h
index 35c3152981..5d7da4b8d1 100644
--- a/modules/md/mod_md_config.h
+++ b/modules/md/mod_md_config.h
@@ -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 */
diff --git a/modules/md/mod_md_drive.c b/modules/md/mod_md_drive.c
index 14c43d5501..5565f44d75 100644
--- a/modules/md/mod_md_drive.c
+++ b/modules/md/mod_md_drive.c
@@ -125,7 +125,7 @@ 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));
- 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) {
diff --git a/modules/md/mod_md_status.c b/modules/md/mod_md_status.c
index 6891ef832e..96c988782b 100644
--- a/modules/md/mod_md_status.c
+++ b/modules/md/mod_md_status.c
@@ -349,32 +349,62 @@ static void si_val_cert_valid_time(status_ctx *ctx, md_json_t *mdj, const status
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);
+ }
+ 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 +703,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 },