From fab9366f041fb4c7b7357d983a354fd981bc9506 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 11 Oct 2017 11:35:19 +0000 Subject: [PATCH] On the trunk: mod_md: v1.0.0, new config directive 'MDNotifyCmd' to hook in a program when Managed Domains have obtained/renewed their certificates successfully. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1811812 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES | 3 ++ docs/manual/mod/mod_md.xml | 16 +++++++++ modules/md/md.h | 2 ++ modules/md/md_util.c | 35 ++++++++++++++++-- modules/md/md_util.h | 5 +++ modules/md/md_version.h | 4 +-- modules/md/mod_md.c | 72 +++++++++++++++++++++++++++++++++++--- modules/md/mod_md_config.c | 23 ++++++++++-- modules/md/mod_md_config.h | 3 ++ 9 files changed, 151 insertions(+), 12 deletions(-) diff --git a/CHANGES b/CHANGES index 9e172201ff..cf1adcb779 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,9 @@ -*- coding: utf-8 -*- Changes with Apache 2.5.0 + *) mod_md: v1.0.0, new config directive 'MDNotifyCmd' to hook in a program when Managed + Domains have obtained/renewed their certificates successfully. [Stefan Eissing] + *) mod_rewrite, core: add the Vary header when a condition evaluates to true and the related RewriteRule is used in a Directory context (triggering an internal redirect). [Luca Toscano] diff --git a/docs/manual/mod/mod_md.xml b/docs/manual/mod/mod_md.xml index 14e369909b..05b973a1f9 100644 --- a/docs/manual/mod/mod_md.xml +++ b/docs/manual/mod/mod_md.xml @@ -351,6 +351,22 @@ MDCertificateAgreement https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2 + + MDNotifyCmd + Run a program when Managed Domain are ready. + MDNotifyCmd path + + server config + + +

The configured executable is run when Managed Domains have signed up or + renewed their certificates. It is given the names of the processed MDs as + arguments. It should return status code 0 to indicate that it has + run successfully. +

+
+
+ MDPortMap Map external to internal ports for domain ownership verification. diff --git a/modules/md/md.h b/modules/md/md.h index af15ad27f8..dfb73a06ed 100644 --- a/modules/md/md.h +++ b/modules/md/md.h @@ -136,6 +136,7 @@ struct md_t { #define MD_KEY_NAME "name" #define MD_KEY_PERMANENT "permanent" #define MD_KEY_PKEY "privkey" +#define MD_KEY_PROCESSED "processed" #define MD_KEY_PROTO "proto" #define MD_KEY_REGISTRATION "registration" #define MD_KEY_RENEW "renew" @@ -156,6 +157,7 @@ struct md_t { #define MD_KEY_VERSION "version" #define MD_FN_MD "md.json" +#define MD_FN_JOB "job.json" #define MD_FN_PRIVKEY "privkey.pem" #define MD_FN_PUBCERT "pubcert.pem" #define MD_FN_CERT "cert.pem" diff --git a/modules/md/md_util.c b/modules/md/md_util.c index ddaedbfe70..bb6f60939d 100644 --- a/modules/md/md_util.c +++ b/modules/md/md_util.c @@ -17,11 +17,10 @@ #include #include -#include +#include #include #include #include -#include #include #include "md_log.h" @@ -743,7 +742,7 @@ apr_status_t md_util_abs_http_uri_check(apr_pool_t *p, const char *uri, const ch return rv; } -/* retry login ************************************************************************************/ +/* try and retry for a while **********************************************************************/ apr_status_t md_util_try(md_util_try_fn *fn, void *baton, int ignore_errs, apr_interval_time_t timeout, apr_interval_time_t start_delay, @@ -787,6 +786,36 @@ apr_status_t md_util_try(md_util_try_fn *fn, void *baton, int ignore_errs, return rv; } +/* execute process ********************************************************************************/ + +apr_status_t md_util_exec(apr_pool_t *p, const char *cmd, const char * const *argv, + int *exit_code) +{ + apr_status_t rv; + apr_procattr_t *procattr; + apr_proc_t *proc; + apr_exit_why_e ewhy; + + *exit_code = 0; + if (!(proc = apr_pcalloc(p, sizeof(*proc)))) { + return APR_ENOMEM; + } + if ( APR_SUCCESS == (rv = apr_procattr_create(&procattr, p)) + && APR_SUCCESS == (rv = apr_procattr_io_set(procattr, APR_NO_FILE, + APR_NO_PIPE, APR_NO_PIPE)) + && APR_SUCCESS == (rv = apr_procattr_cmdtype_set(procattr, APR_PROGRAM)) + && APR_SUCCESS == (rv = apr_proc_create(proc, cmd, argv, NULL, procattr, p)) + && APR_CHILD_DONE == (rv = apr_proc_wait(proc, exit_code, &ewhy, APR_WAIT))) { + /* let's not dwell on exit stati, but core should signal something's bad */ + if (*exit_code > 127 || APR_PROC_SIGNAL_CORE == ewhy) { + return APR_EINCOMPLETE; + } + return APR_SUCCESS; + } + return rv; +} + + /* date/time encoding *****************************************************************************/ const char *md_print_duration(apr_pool_t *p, apr_interval_time_t duration) diff --git a/modules/md/md_util.h b/modules/md/md_util.h index 1f384b5a71..a69635bdb0 100644 --- a/modules/md/md_util.h +++ b/modules/md/md_util.h @@ -52,6 +52,11 @@ struct apr_array_header_t *md_array_str_remove(apr_pool_t *p, struct apr_array_h int md_array_str_add_missing(struct apr_array_header_t *dest, struct apr_array_header_t *src, int case_sensitive); +/**************************************************************************************************/ +/* process execution */ +apr_status_t md_util_exec(apr_pool_t *p, const char *cmd, const char * const *argv, + int *exit_code); + /**************************************************************************************************/ /* dns name check */ diff --git a/modules/md/md_version.h b/modules/md/md_version.h index d29daad4dd..467b864199 100644 --- a/modules/md/md_version.h +++ b/modules/md/md_version.h @@ -26,7 +26,7 @@ * @macro * Version number of the md module as c string */ -#define MOD_MD_VERSION "0.9.9" +#define MOD_MD_VERSION "1.0.0" /** * @macro @@ -34,7 +34,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 0x000909 +#define MOD_MD_VERSION_NUM 0x010000 #define MD_EXPERIMENTAL 0 #define MD_ACME_DEF_URL "https://acme-v01.api.letsencrypt.org/directory" diff --git a/modules/md/mod_md.c b/modules/md/mod_md.c index 381371bc4d..4aa9c14535 100644 --- a/modules/md/mod_md.c +++ b/modules/md/mod_md.c @@ -34,6 +34,7 @@ #include "md_curl.h" #include "md_crypt.h" #include "md_http.h" +#include "md_json.h" #include "md_store.h" #include "md_store_fs.h" #include "md_log.h" @@ -557,6 +558,7 @@ typedef struct { typedef struct { apr_pool_t *p; server_rec *s; + md_mod_conf_t *mc; ap_watchdog_t *watchdog; apr_time_t next_change; @@ -725,18 +727,77 @@ static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp) if (restart) { const char *action, *names = ""; + md_json_t *jprops; + md_store_t *store = md_reg_store_get(wd->reg); int n; for (i = 0, n = 0; i < wd->jobs->nelts; ++i) { job = APR_ARRAY_IDX(wd->jobs, i, md_job_t *); if (job->need_restart && !job->restart_processed) { - names = apr_psprintf(ptemp, "%s%s%s", names, n? ", " : "", job->md->name); - ++n; - job->restart_processed = 1; + rv = md_store_load_json(store, MD_SG_STAGING, job->md->name, + "job.json", &jprops, ptemp); + if (APR_SUCCESS == rv) { + job->restart_processed = md_json_getb(jprops, MD_KEY_PROCESSED, NULL); + } + if (!job->restart_processed) { + names = apr_psprintf(ptemp, "%s%s%s", names, n? " " : "", job->md->name); + ++n; + } } } if (n > 0) { + int notified = 1; + + /* Run notifiy command for ready MDs (if configured) and persist that + * we have done so. This process might be reaped after n requests or die + * of another cause. The one taking over the watchdog need to notify again. + */ + if (wd->mc->notify_cmd) { + const char * const *argv; + const char *cmdline; + int exit_code; + + cmdline = apr_psprintf(ptemp, "%s %s", wd->mc->notify_cmd, names); + apr_tokenize_to_argv(cmdline, (char***)&argv, ptemp); + if (APR_SUCCESS == (rv = md_util_exec(ptemp, argv[0], argv, &exit_code))) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, wd->s, APLOGNO() + "notify command '%s' returned %d", + wd->mc->notify_cmd, exit_code); + } + else { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, wd->s, APLOGNO() + "executing configured MDNotifyCmd %s", wd->mc->notify_cmd); + notified = 0; + } + } + + if (notified) { + /* persist the jobs that were notified */ + for (i = 0, n = 0; i < wd->jobs->nelts; ++i) { + job = APR_ARRAY_IDX(wd->jobs, i, md_job_t *); + if (job->need_restart && !job->restart_processed) { + job->restart_processed = 1; + + rv = md_store_load_json(store, MD_SG_STAGING, job->md->name, + MD_FN_JOB, &jprops, ptemp); + if (APR_SUCCESS == rv) { + md_json_setb(1, jprops, MD_KEY_PROCESSED, NULL); + rv = md_store_save_json(store, ptemp, MD_SG_STAGING, job->md->name, + MD_FN_JOB, jprops, 0); + } + } + } + } + + /* FIXME: the server needs to start gracefully to take the new certficate in. + * This poses a variety of problems to solve satisfactory for everyone: + * - I myself, have no implementation for Windows + * - on *NIX, child processes run with less privileges, preventing + * the signal based restart trigger to work + * - admins want better control of timing windows for restarts, e.g. + * during less busy hours/days. + */ rv = md_server_graceful(ptemp, wd->s); if (APR_ENOTIMPL == rv) { /* self-graceful restart not supported in this setup */ @@ -755,7 +816,7 @@ static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp) } static apr_status_t start_watchdog(apr_array_header_t *names, apr_pool_t *p, - md_reg_t *reg, server_rec *s) + md_reg_t *reg, server_rec *s, md_mod_conf_t *mc) { apr_allocator_t *allocator; md_watchdog *wd; @@ -790,6 +851,7 @@ static apr_status_t start_watchdog(apr_array_header_t *names, apr_pool_t *p, wd->p = wdp; wd->reg = reg; wd->s = s; + wd->mc = mc; wd->jobs = apr_array_make(wd->p, 10, sizeof(md_job_t *)); for (i = 0; i < names->nelts; ++i) { @@ -943,7 +1005,7 @@ static apr_status_t md_post_config(apr_pool_t *p, apr_pool_t *plog, load_stage_sets(drive_names, p, reg, s); md_http_use_implementation(md_curl_get_impl(p)); - rv = start_watchdog(drive_names, p, reg, s); + rv = start_watchdog(drive_names, p, reg, s, mc); } else { ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10075) diff --git a/modules/md/mod_md_config.c b/modules/md/mod_md_config.c index 75926d4da1..33cc274433 100644 --- a/modules/md/mod_md_config.c +++ b/modules/md/mod_md_config.c @@ -39,13 +39,14 @@ #define MD_CMD_DRIVEMODE "MDDriveMode" #define MD_CMD_MEMBER "MDMember" #define MD_CMD_MEMBERS "MDMembers" -#define MD_CMD_MUST_STAPLE "MDMustStaple" +#define MD_CMD_MUSTSTAPLE "MDMustStaple" #define MD_CMD_PORTMAP "MDPortMap" #define MD_CMD_PKEYS "MDPrivateKeys" #define MD_CMD_PROXY "MDHttpProxy" #define MD_CMD_RENEWWINDOW "MDRenewWindow" #define MD_CMD_REQUIREHTTPS "MDRequireHttps" #define MD_CMD_STOREDIR "MDStoreDir" +#define MD_CMD_NOTIFYCMD "MDNotifyCmd" #define DEF_VAL (-1) @@ -62,6 +63,7 @@ static md_mod_conf_t defmc = { MD_HSTS_MAX_AGE_DEFAULT, NULL, NULL, + NULL, }; /* Default server specific setting */ @@ -745,6 +747,19 @@ static const char *md_config_set_pkeys(cmd_parms *cmd, void *dc, return apr_pstrcat(cmd->pool, "unsupported private key type \"", ptype, "\"", NULL); } +static const char *md_config_set_notify_cmd(cmd_parms *cmd, void *arg, const char *value) +{ + md_srv_conf_t *sc = md_config_get(cmd->server); + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err) { + return err; + } + sc->mc->notify_cmd = value; + (void)arg; + return NULL; +} + const command_rec md_cmds[] = { AP_INIT_TAKE1( MD_CMD_CA, md_config_set_ca, NULL, RSRC_CONF, "URL of CA issueing the certificates"), @@ -766,7 +781,7 @@ const command_rec md_cmds[] = { AP_INIT_TAKE_ARGV( MD_CMD_MEMBERS, md_config_sec_add_members, NULL, RSRC_CONF, "Define domain name(s) part of the Managed Domain. Use 'auto' or " "'manual' to enable/disable auto adding names from virtual hosts."), - AP_INIT_TAKE1( MD_CMD_MUST_STAPLE, md_config_set_must_staple, NULL, RSRC_CONF, + AP_INIT_TAKE1( MD_CMD_MUSTSTAPLE, md_config_set_must_staple, NULL, RSRC_CONF, "Enable/Disable the Must-Staple flag for new certificates."), AP_INIT_TAKE12( MD_CMD_PORTMAP, md_config_set_port_map, NULL, RSRC_CONF, "Declare the mapped ports 80 and 443 on the local server. E.g. 80:8000 " @@ -783,6 +798,8 @@ const command_rec md_cmds[] = { "Time length for renewal before certificate expires (defaults to days)"), AP_INIT_TAKE1( MD_CMD_REQUIREHTTPS, md_config_set_require_https, NULL, RSRC_CONF, "Redirect non-secure requests to the https: equivalent."), + AP_INIT_TAKE1( MD_CMD_NOTIFYCMD, md_config_set_notify_cmd, NULL, RSRC_CONF, + "set the command to run when signup/renew of domain is complete."), AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL) }; @@ -844,6 +861,8 @@ const char *md_config_gets(const md_srv_conf_t *sc, md_config_var_t var) return sc->mc->proxy_url; case MD_CONFIG_CA_AGREEMENT: return sc->ca_agreement? sc->ca_agreement : defconf.ca_agreement; + case MD_CONFIG_NOTIFY_CMD: + return sc->mc->notify_cmd; default: return NULL; } diff --git a/modules/md/mod_md_config.h b/modules/md/mod_md_config.h index 43b8259ee1..2b2363404f 100644 --- a/modules/md/mod_md_config.h +++ b/modules/md/mod_md_config.h @@ -34,6 +34,7 @@ typedef enum { MD_CONFIG_PROXY, MD_CONFIG_REQUIRE_HTTPS, MD_CONFIG_MUST_STAPLE, + MD_CONFIG_NOTIFY_CMD, } md_config_var_t; typedef struct { @@ -49,6 +50,8 @@ typedef struct { int hsts_max_age; /* max-age of HSTS (rfc6797) header */ const char *hsts_header; /* computed HTST header to use or NULL */ apr_array_header_t *unused_names; /* post config, names of all MDs not assigned to a vhost */ + + const char *notify_cmd; /* notification command to execute on signup/renew */ } md_mod_conf_t; typedef struct md_srv_conf_t { -- 2.40.0