]> granicus.if.org Git - apache/commitdiff
On the trunk:
authorStefan Eissing <icing@apache.org>
Wed, 11 Oct 2017 11:35:19 +0000 (11:35 +0000)
committerStefan Eissing <icing@apache.org>
Wed, 11 Oct 2017 11:35:19 +0000 (11:35 +0000)
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
docs/manual/mod/mod_md.xml
modules/md/md.h
modules/md/md_util.c
modules/md/md_util.h
modules/md/md_version.h
modules/md/mod_md.c
modules/md/mod_md_config.c
modules/md/mod_md_config.h

diff --git a/CHANGES b/CHANGES
index 9e172201ffd4ff5fd49954c13b2872c1307cd5aa..cf1adcb779875c6166008e23aafdf1a2e9db5b37 100644 (file)
--- 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]
index 14e369909b25d0736d62496387d91a0aa223e3c6..05b973a1f9cfa921b913861718031e71362209c4 100644 (file)
@@ -351,6 +351,22 @@ MDCertificateAgreement https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2
         </usage>
     </directivesynopsis>
 
+    <directivesynopsis>
+        <name>MDNotifyCmd</name>
+        <description>Run a program when Managed Domain are ready.</description>
+        <syntax>MDNotifyCmd  path</syntax>
+        <contextlist>
+            <context>server config</context>
+        </contextlist>
+        <usage>
+            <p>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.
+            </p>
+        </usage>
+    </directivesynopsis>
+
     <directivesynopsis>
         <name>MDPortMap</name>
         <description>Map external to internal ports for domain ownership verification.</description>
index af15ad27f86b45ad810131e06fd3e30a5e8231cf..dfb73a06ed586b37c98d3c6a862eb47c7250b089 100644 (file)
@@ -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"
index ddaedbfe70c57ab4057c11e5a5f82816c6c97715..bb6f60939d528200c5835feececcfe5d46c99a47 100644 (file)
 
 #include <apr_lib.h>
 #include <apr_strings.h>
-#include <apr_file_io.h>
+#include <apr_portable.h>
 #include <apr_file_info.h>
 #include <apr_fnmatch.h>
 #include <apr_tables.h>
-#include <apr_time.h>
 #include <apr_uri.h>
 
 #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)
index 1f384b5a71b3a77b101d43cb51b8e450efd554b1..a69635bdb0883a483602a736240db2db027dac3f 100644 (file)
@@ -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 */
 
index d29daad4ddd618bfcb9bc4236dd7e0f290247672..467b8641991a251208b590265272329b6a393ab1 100644 (file)
@@ -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"
index 381371bc4daa30c120dd73d723861ff874ab60e8..4aa9c145354f2fb9b0f8e8cae25b2a79a8cb0c01 100644 (file)
@@ -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)
index 75926d4da14e65a04ab1c8c4f6fa461622d8e7f2..33cc2744331979abe0ee54a16b2d3a04494bcb02 100644 (file)
 #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;
     }
index 43b8259ee1d532c1dde3f06e7986d4da7a87f78f..2b2363404fa851ee5020c999a3f6d4e04fa41b5b 100644 (file)
@@ -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 {