]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
mod_md: Support CA-specific HTTP proxy settings
authorRainer Jung <rjung@apache.org>
Tue, 16 Jun 2026 18:49:37 +0000 (18:49 +0000)
committerRainer Jung <rjung@apache.org>
Tue, 16 Jun 2026 18:49:37 +0000 (18:49 +0000)
Allow the MDHttpProxy setting to be set per MDomain.
The global MDHttpProxy setting is used by default.
Add integration tests.

Merge from icing/md:
https://github.com/icing/mod_md/commit/f08cd11251dd73e8038b557cc56e828ac4c70f50

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1935434 13f79535-47bb-0310-9956-ffa450edef68

modules/md/md.h
modules/md/md_acme.c
modules/md/md_acme_drive.c
modules/md/md_core.c
modules/md/md_http.c
modules/md/md_reg.c
modules/md/mod_md.c
modules/md/mod_md_config.c
modules/md/mod_md_config.h
test/modules/md/md_conf.py
test/modules/md/test_702_auto.py

index fb1a270ac8fc6c3ace928d094260faa550da60cd..691c19427789e3d60d588b2f9ba830864d89ad6f 100644 (file)
@@ -100,6 +100,7 @@ struct md_t {
     
     struct apr_array_header_t *acme_tls_1_domains; /* domains supporting "acme-tls/1" protocol */
     const char *dns01_cmd;          /* DNS challenge command, override global command */
+    const char *proxy_url;          /* Proxy URL, override global command */
 
     const struct md_srv_conf_t *sc; /* server config where it was defined or NULL */
     const char *defn_name;          /* config file this MD was defined */
@@ -185,6 +186,7 @@ struct md_t {
 #define MD_KEY_PROFILE          "profile"
 #define MD_KEY_PROFILE_MANDATORY "profile-mandatory"
 #define MD_KEY_PROTO            "proto"
+#define MD_KEY_PROXY_URL        "proxy-url"
 #define MD_KEY_READY            "ready"
 #define MD_KEY_REGISTRATION     "registration"
 #define MD_KEY_RENEW            "renew"
index 7c876c62c9e640039717e427601ddd37fc4457bf..1bd565d623c32aa3743c99e50b84f6017dbdcb32 100644 (file)
@@ -647,7 +647,7 @@ apr_status_t md_acme_create(md_acme_t **pacme, apr_pool_t *p, const char *url,
     acme->p = p;
     acme->user_agent = apr_psprintf(p, "%s mod_md/%s", 
                                     base_product, MOD_MD_VERSION);
-    acme->proxy_url = proxy_url? apr_pstrdup(p, proxy_url) : NULL;
+    acme->proxy_url = apr_pstrdup(p, proxy_url);
     acme->max_retries = 99;
     acme->ca_file = ca_file;
 
index 94bcc8aab47b9746d8addea7dea6c82cf5d64ad1..dd9cf8d5f2e08116ea08a041249bb94491c1a697 100644 (file)
@@ -771,7 +771,8 @@ static apr_status_t acme_renew(md_proto_driver_t *d, md_result_t *result)
     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))) {
+                                            ad->md->proxy_url ? ad->md->proxy_url : d->proxy_url,
+                                            d->ca_file))) {
         md_result_printf(result, rv, "setup ACME communications");
         md_result_log(result, MD_LOG_ERR);
         goto out;
@@ -1033,7 +1034,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_effective,
-                                                d->proxy_url, d->ca_file))) {
+                                                d->md->proxy_url ? d->md->proxy_url : d->proxy_url,
+                                                d->ca_file))) {
             md_result_set(result, rv, "error setting up acme");
             goto leave;
         }
@@ -1142,7 +1144,8 @@ static apr_status_t acme_get_ari(md_proto_driver_t *d,
     }
 
     if (APR_SUCCESS != (rv = md_acme_create(&ad->acme, d->p, ca_effective,
-                                            d->proxy_url, d->ca_file))) {
+                                            d->md->proxy_url ? d->md->proxy_url : d->proxy_url,
+                                            d->ca_file))) {
         md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p,
                       "create ACME communications");
         goto out;
index d47c4463287958db849755d9e22a8a456f16e0b5..227f5ab3dbfc59ab57655978562657a924bb4321 100644 (file)
@@ -258,6 +258,7 @@ md_t *md_clone(apr_pool_t *p, const md_t *src)
         md->acme_tls_1_domains = md_array_str_compact(p, src->acme_tls_1_domains, 0);
         md->stapling = src->stapling;
         if (src->dns01_cmd) md->dns01_cmd = apr_pstrdup(p, src->dns01_cmd);
+        if (src->proxy_url) md->proxy_url = apr_pstrdup(p, src->proxy_url);
         if (src->cert_files) md->cert_files = md_array_str_clone(p, src->cert_files);
         if (src->pkey_files) md->pkey_files = md_array_str_clone(p, src->pkey_files);
     }    
@@ -315,6 +316,7 @@ md_json_t *md_to_json(const md_t *md, apr_pool_t *p)
         if (md->pkey_files) md_json_setsa(md->pkey_files, json, MD_KEY_PKEY_FILES, NULL);
         md_json_setb(md->stapling > 0, json, MD_KEY_STAPLING, NULL);
         if (md->dns01_cmd) md_json_sets(md->dns01_cmd, json, MD_KEY_CMD_DNS01, NULL);
+        if (md->proxy_url) md_json_sets(md->proxy_url, json, MD_KEY_PROXY_URL, NULL);
         if (md->ca_eab_kid && strcmp("none", md->ca_eab_kid)) {
             md_json_sets(md->ca_eab_kid, json, MD_KEY_EAB, MD_KEY_KID, NULL);
             if (md->ca_eab_hmac) md_json_sets(md->ca_eab_hmac, json, MD_KEY_EAB, MD_KEY_HMAC, NULL);
@@ -384,6 +386,7 @@ md_t *md_from_json(md_json_t *json, apr_pool_t *p)
         }
         md->stapling = (int)md_json_getb(json, MD_KEY_STAPLING, NULL);
         md->dns01_cmd = md_json_dups(p, json, MD_KEY_CMD_DNS01, NULL);
+        md->proxy_url = md_json_dups(p, json, MD_KEY_PROXY_URL, NULL);
         if (md_json_has_key(json, MD_KEY_EAB, NULL)) {
             md->ca_eab_kid = md_json_dups(p, json, MD_KEY_EAB, MD_KEY_KID, NULL);
             md->ca_eab_hmac = md_json_dups(p, json, MD_KEY_EAB, MD_KEY_HMAC, NULL);
index 283f4be13528bf20c900114237dea5291528fa84..11f10a6b1bbee9c3a1f8b336907bacbb0697eeaa 100644 (file)
@@ -82,7 +82,7 @@ apr_status_t md_http_create(md_http_t **phttp, apr_pool_t *p, const char *user_a
     http->pool = p;
     http->impl = cur_impl;
     http->user_agent = apr_pstrdup(p, user_agent);
-    http->proxy_url = proxy_url? apr_pstrdup(p, proxy_url) : NULL;
+    http->proxy_url = apr_pstrdup(p, proxy_url);
     http->bucket_alloc = apr_bucket_alloc_create(p);
     if (!http->bucket_alloc) {
         return APR_EGENERAL;
index 36d1944b879973ffa0e78afcdd5a50fd96217a36..d7d13a2ca46bd042558ed3d9f09fb7e3ab78f675 100644 (file)
@@ -110,7 +110,7 @@ apr_status_t md_reg_create(md_reg_t **preg, apr_pool_t *p, struct md_store_t *st
     reg->certs = apr_hash_make(p);
     reg->can_http = 1;
     reg->can_https = 1;
-    reg->proxy_url = proxy_url? apr_pstrdup(p, proxy_url) : NULL;
+    reg->proxy_url = apr_pstrdup(p, proxy_url);
     reg->ca_file = (ca_file && apr_cstr_casecmp("none", ca_file))?
                     apr_pstrdup(p, ca_file) : NULL;
     reg->min_delay = min_delay;
index 349d18750c8b813a4a17cc0014d198ccbcfe310b..458e943cfa3a8d592ec8c760ee051e6799057ae2 100644 (file)
@@ -855,6 +855,7 @@ static apr_status_t md_post_config_before_ssl(apr_pool_t *p, apr_pool_t *plog,
     apr_status_t rv = APR_SUCCESS;
     int dry_run = 0, log_level = APLOG_DEBUG;
     md_store_t *store;
+    const char *proxy_url;
 
     apr_pool_userdata_get(&data, mod_md_init_key, s->process->pool);
     if (data == NULL) {
@@ -893,7 +894,9 @@ static apr_status_t md_post_config_before_ssl(apr_pool_t *p, apr_pool_t *plog,
     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,
+    proxy_url = apr_table_get(mc->env, MD_KEY_PROXY_URL);
+
+    rv = md_reg_create(&mc->reg, p, store, proxy_url, mc->ca_certs,
                        mc->min_delay, mc->retry_failover,
                        mc->use_store_locks, mc->lock_wait_timeout);
     if (APR_SUCCESS != rv) {
@@ -903,7 +906,7 @@ static apr_status_t md_post_config_before_ssl(apr_pool_t *p, apr_pool_t *plog,
 
     /* 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, proxy_url,
                           mc->min_delay);
     if (APR_SUCCESS != rv) {
         ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10196) "setup ocsp registry");
index fa118201ed23050f92315b0cf1d285490faafc82..fe977afdd84e5182e9ca9fac20dd537c792d72a9 100644 (file)
@@ -61,7 +61,6 @@ static md_mod_conf_t defmc = {
 #else
     MD_DEFAULT_BASE_DIR,
 #endif
-    NULL,                      /* proxy url for outgoing http */
     NULL,                      /* md_reg_t */
     NULL,                      /* md_ocsp_reg_t */
     80,                        /* local http: port */
@@ -127,6 +126,7 @@ static md_srv_conf_t defconf = {
     1,                         /* staple others */
     1,                         /* ACME ARI renewals */
     NULL,                      /* dns01_cmd */
+    NULL,                      /* proxy URL */
     NULL,                      /* currently defined md */
     NULL,                      /* assigned md, post config */
     0,                         /* is_ssl, set during mod_ssl post_config */
@@ -185,6 +185,7 @@ static void srv_conf_props_clear(md_srv_conf_t *sc)
     sc->staple_others = DEF_VAL;
     sc->ari_renewals = DEF_VAL;
     sc->dns01_cmd = NULL;
+    sc->proxy_url = NULL;
 }
 
 static void srv_conf_props_copy(md_srv_conf_t *to, const md_srv_conf_t *from)
@@ -209,6 +210,7 @@ static void srv_conf_props_copy(md_srv_conf_t *to, const md_srv_conf_t *from)
     to->staple_others = from->staple_others;
     to->ari_renewals = from->ari_renewals;
     to->dns01_cmd = from->dns01_cmd;
+    to->proxy_url = from->proxy_url;
 }
 
 static void srv_conf_props_apply(md_t *md, const md_srv_conf_t *from, apr_pool_t *p)
@@ -236,6 +238,7 @@ static void srv_conf_props_apply(md_t *md, const md_srv_conf_t *from, apr_pool_t
     if (from->ari_renewals != DEF_VAL) md->ari_renewals = from->ari_renewals;
     if (from->stapling != DEF_VAL) md->stapling = from->stapling;
     if (from->dns01_cmd) md->dns01_cmd = from->dns01_cmd;
+    if (from->proxy_url) md->proxy_url = from->proxy_url;
 }
 
 void *md_config_create_svr(apr_pool_t *pool, server_rec *s)
@@ -285,6 +288,7 @@ static void *md_config_merge(apr_pool_t *pool, void *basev, void *addv)
     nsc->staple_others = (add->staple_others != DEF_VAL)? add->staple_others : base->staple_others;
     nsc->ari_renewals = (add->ari_renewals != DEF_VAL)? add->ari_renewals : base->ari_renewals;
     nsc->dns01_cmd = (add->dns01_cmd)? add->dns01_cmd : base->dns01_cmd;
+    nsc->proxy_url = (add->proxy_url)? add->proxy_url : base->proxy_url;
     nsc->current = NULL;
     
     return nsc;
@@ -865,14 +869,20 @@ static const char *md_config_set_proxy(cmd_parms *cmd, void *arg, const char *va
     md_srv_conf_t *sc = md_config_get(cmd->server);
     const char *err;
 
-    if ((err = md_conf_check_location(cmd, MD_LOC_NOT_MD))) {
+    if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) {
         return err;
     }
     md_util_abs_http_uri_check(cmd->pool, value, &err);
     if (err) {
         return err;
     }
-    sc->mc->proxy_url = value;
+
+    if (inside_md_section(cmd)) {
+        sc->proxy_url = value;
+    } else {
+        apr_table_set(sc->mc->env, MD_KEY_PROXY_URL, value);
+    }
+
     (void)arg;
     return NULL;
 }
@@ -1471,8 +1481,6 @@ const char *md_config_gets(const md_srv_conf_t *sc, md_config_var_t var)
             return sc->ca_proto? sc->ca_proto : defconf.ca_proto;
         case MD_CONFIG_BASE_DIR:
             return sc->mc->base_dir;
-        case MD_CONFIG_PROXY:
-            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:
index 3159ec651f7186d53862ad161592af08d6dcc5da..1828a2e310e3b776a965c3e999093729f9264ac7 100644 (file)
@@ -32,7 +32,6 @@ typedef enum {
     MD_CONFIG_RENEW_WINDOW,
     MD_CONFIG_WARN_WINDOW,
     MD_CONFIG_TRANSITIVE,
-    MD_CONFIG_PROXY,
     MD_CONFIG_REQUIRE_HTTPS,
     MD_CONFIG_MUST_STAPLE,
     MD_CONFIG_NOTIFY_CMD,
@@ -53,7 +52,6 @@ typedef struct md_mod_conf_t md_mod_conf_t;
 struct md_mod_conf_t {
     apr_array_header_t *mds;           /* all md_t* defined in the config, shared */
     const char *base_dir;              /* base dir for store */
-    const char *proxy_url;             /* proxy url to use (or NULL) */
     struct md_reg_t *reg;              /* md registry instance */
     struct md_ocsp_reg_t *ocsp;        /* ocsp status registry */
 
@@ -115,6 +113,7 @@ typedef struct md_srv_conf_t {
     int ari_renewals;                  /* ACME ARI extension enabled */
 
     const char *dns01_cmd;             /* DNS challenge command, override global command */
+    const char *proxy_url;             /* Proxy URL, override global command */
 
     md_t *current;                     /* md currently defined in <MDomainSet xxx> section */
     struct apr_array_header_t *assigned; /* post_config: MDs that apply to this server */
index 54b10abc65905f03c68e32f788b00a1a44cd2294..95e10f4cf06d3351d9b60e809ffbd5fbb8023b7f 100755 (executable)
@@ -46,7 +46,7 @@ class MDConf(HttpdConf):
                 "    ProxyRequests On",
                 "    ProxyVia On",
                 "    # be totally open",
-                "    AllowCONNECT 0-56535",
+                "    AllowCONNECT 0-65535",
                 "    <Proxy *>",
                 "       # No require or other restrictions, this is just a test server",
                 "    </Proxy>",
index 7246b62255cee66b32c17c5d9d14f6d4dfe66018..1536d39624fc87d6647939e5142fbb5d87ea6137 100644 (file)
@@ -297,6 +297,55 @@ class TestAutov2:
         assert env.apache_restart() == 0, f'{env.apachectl_stderr}'
         env.check_md_complete(domain)
 
+    # Specify a valid http proxy for a single MDomain
+    def test_md_702_008b(self, env):
+        domain = self.test_domain
+        domains = [domain]
+        #
+        conf = MDConf(env, admin=f"admin@{domain}", proxy=True)
+        conf.add_drive_mode("always")
+        conf.start_md(domains)
+        conf.add(f"    MDHttpProxy http://localhost:{env.proxy_port}")
+        conf.end_md()
+        conf.install()
+        #
+        # - restart (-> drive), check that md is in store
+        assert env.apache_restart() == 0, f'{env.apachectl_stderr}'
+        assert env.await_completion([domain])
+        assert env.apache_restart() == 0, f'{env.apachectl_stderr}'
+        env.check_md_complete(domain)
+
+    # Specify a non-working http proxy for MDomain A and a valid http proxy for MDomain B
+    def test_md_702_008c(self, env):
+        domain_a = f"a{self.test_domain}"
+        domain_b = f"b{self.test_domain}"
+        conf = MDConf(env, admin=f"admin@{domain_a}", proxy=True)
+        conf.start_md([domain_a])
+        conf.add(f"    MDHttpProxy http://localhost:1")
+        conf.end_md()
+        conf.add_vhost(domains=[domain_a])
+        conf.start_md([domain_b])
+        conf.add(f"    MDHttpProxy http://localhost:{env.proxy_port}")
+        conf.end_md()
+        conf.add_vhost(domains=[domain_b])
+        conf.install()
+        assert env.apache_restart() == 0, f'{env.apachectl_stderr}'
+        assert env.await_completion([domain_b], restart=False)
+        md = env.await_error(domain_a)
+        assert md
+        assert md['renewal']['errors'] > 0
+        assert md['renewal']['last']['status-description'] == 'Connection refused'
+        assert 'account' not in md['ca']
+        #
+        env.httpd_error_log.ignore_recent(
+            lognos = [
+                "AH10056"   # Unsuccessful in contacting ACME server
+            ],
+            matches = [
+                r'.*Unsuccessful in contacting ACME server at .*'
+            ]
+        )
+
     # Force cert renewal due to critical remaining valid duration
     # Assert that new cert activation is delayed
     def test_md_702_009(self, env, acme):