From: Rainer Jung Date: Tue, 16 Jun 2026 19:28:00 +0000 (+0000) Subject: mod_md: New directive: MDHttpProxyCACertificateFile X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=97bb11ecadea726a6032ba96e3a7a1c612b403a4;p=thirdparty%2Fapache%2Fhttpd.git mod_md: New directive: MDHttpProxyCACertificateFile Sets the CA certificates to use for connections to the HTTPS proxy that has been configured with MDHttpProxy. Merge from icing/md: https://github.com/icing/mod_md/commit/b7c956391aa116129e24a0191eec74616ff6b1a0 git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1935440 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/docs/manual/mod/mod_md.xml b/docs/manual/mod/mod_md.xml index 3ed739d0e8..f4aedc8592 100644 --- a/docs/manual/mod/mod_md.xml +++ b/docs/manual/mod/mod_md.xml @@ -124,7 +124,7 @@ Protocols h2 http/1.1 acme-tls/1

And the `tls-alpn-01` challenge type is available. -

+

Wildcard Certificates @@ -540,12 +540,41 @@ MDCertificateAuthority https://acme-staging-v02.api.letsencrypt.org/directory Since version 2.4.69, a proxy can be configured separately for each MDomain. -

Use the given http forward proxy URL to connect to the MDCertificateAuthority. +

+ Use the given http forward proxy URL to connect to the MDCertificateAuthority. Define this if your webserver can only reach the internet with a forward proxy.

+ + MDHttpProxyCACertificateFile + Sets the root (CA) certificates to use for TLS connections to the http-proxy. + MDHttpProxyCACertificateFile path-to-pem-file + MDHttpProxyCACertificateFile none + + server config + + Available in version 2.4.69 and later + +

+ This is used for connections to the HTTPS forward proxy (MDHttpProxy). + It is needed if the certificate of the HTTPS proxy cannot be verified using the general CA root store. + This is sometimes the case in test setups or enterprise environments. +

+

+ The certificate of the ACME server is verified with the root certificates set by + MDCACertificateFile, so you might need to use both settings. +

+

+ Use "none" as path to disable explicitly. +

+

+ This can be configured separately for each MDomain. +

+
+
+ MDMember Additional hostname for the managed domain. @@ -1623,6 +1652,7 @@ MDMessageCmd /etc/apache/md-message server config + Since version 2.4.69, this can be configured separately for each MDomain.

This is mainly used in test setups where the module needs to diff --git a/modules/md/md.h b/modules/md/md.h index 415a9dde72..da6bdefc9b 100644 --- a/modules/md/md.h +++ b/modules/md/md.h @@ -103,6 +103,8 @@ struct md_t { const char *proxy_url; /* Proxy URL, override global command */ const char *ca_certs; /* root certificates to use for connections, override global command */ + const char *proxy_ca_certs; /* root certificates to use for proxy connections, + 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 */ unsigned defn_line_number; /* line number of definition */ @@ -188,6 +190,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_CA_CERTS "proxy-ca-certs" #define MD_KEY_PROXY_URL "proxy-url" #define MD_KEY_READY "ready" #define MD_KEY_REGISTRATION "registration" diff --git a/modules/md/md_acme.c b/modules/md/md_acme.c index 2225ca96ee..3b102e65e5 100644 --- a/modules/md/md_acme.c +++ b/modules/md/md_acme.c @@ -624,7 +624,8 @@ apr_status_t md_acme_POST_new_account(md_acme_t *acme, /* ACME setup */ apr_status_t md_acme_create(md_acme_t **pacme, apr_pool_t *p, const char *url, - const char *proxy_url, const char *ca_file) + const char *proxy_url, const char *ca_file, + const char *proxy_ca_file) { md_acme_t *acme; const char *err = NULL; @@ -650,6 +651,7 @@ apr_status_t md_acme_create(md_acme_t **pacme, apr_pool_t *p, const char *url, acme->proxy_url = apr_pstrdup(p, proxy_url); acme->max_retries = 9; acme->ca_file = ca_file; + acme->proxy_ca_file = proxy_ca_file; if (APR_SUCCESS != (rv = apr_uri_parse(p, url, &uri_parsed))) { md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "parsing ACME uri: %s", url); @@ -802,6 +804,7 @@ apr_status_t md_acme_setup(md_acme_t *acme, md_result_t *result) md_http_set_connect_timeout_default(acme->http, apr_time_from_sec(30)); md_http_set_stalling_default(acme->http, 10, apr_time_from_sec(30)); md_http_set_ca_file(acme->http, acme->ca_file); + md_http_set_proxy_ca_file(acme->http, acme->proxy_ca_file); md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p, "get directory from %s", acme->url); diff --git a/modules/md/md_acme.h b/modules/md/md_acme.h index 6f9c9aa019..76c27a88dd 100644 --- a/modules/md/md_acme.h +++ b/modules/md/md_acme.h @@ -98,6 +98,7 @@ struct md_acme_t { const char *user_agent; const char *proxy_url; const char *ca_file; + const char *proxy_ca_file; const char *acct_id; /* local storage id account was loaded from or NULL */ struct md_acme_acct_t *acct; /* account at ACME server to use for requests */ @@ -152,9 +153,11 @@ apr_status_t md_acme_init(apr_pool_t *pool, const char *base_version, int init_s * @param url url of the server, optional if known at path * @param proxy_url optional url of a HTTP(S) proxy to use * @param ca_file optional CA trust anchor file to use + * @param proxy_ca_file optional CA trust anchor file to use for the HTTP proxy */ apr_status_t md_acme_create(md_acme_t **pacme, apr_pool_t *p, const char *url, - const char *proxy_url, const char *ca_file); + const char *proxy_url, const char *ca_file, + const char *proxy_ca_file); /** * Contact the ACME server and retrieve its directory information. diff --git a/modules/md/md_acme_drive.c b/modules/md/md_acme_drive.c index 76e59bfe3d..bef07e52d9 100644 --- a/modules/md/md_acme_drive.c +++ b/modules/md/md_acme_drive.c @@ -772,7 +772,8 @@ static apr_status_t acme_renew(md_proto_driver_t *d, md_result_t *result) d->md->name, ca_effective); if (APR_SUCCESS != (rv = md_acme_create(&ad->acme, d->p, ca_effective, ad->md->proxy_url ? ad->md->proxy_url : d->proxy_url, - ad->md->ca_certs ? ad->md->ca_certs : d->ca_certs))) { + ad->md->ca_certs ? ad->md->ca_certs : d->ca_certs, + ad->md->proxy_ca_certs ? ad->md->proxy_ca_certs : d->proxy_ca_certs))) { md_result_printf(result, rv, "setup ACME communications"); md_result_log(result, MD_LOG_ERR); goto out; @@ -1035,7 +1036,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->md->proxy_url ? d->md->proxy_url : d->proxy_url, - d->md->ca_certs ? d->md->ca_certs : d->ca_certs))) { + d->md->ca_certs ? d->md->ca_certs : d->ca_certs, + d->md->proxy_ca_certs ? d->md->proxy_ca_certs : d->proxy_ca_certs))) { md_result_set(result, rv, "error setting up acme"); goto leave; } @@ -1145,7 +1147,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->md->proxy_url ? d->md->proxy_url : d->proxy_url, - d->md->ca_certs ? d->md->ca_certs : d->ca_certs))) { + d->md->ca_certs ? d->md->ca_certs : d->ca_certs, + d->md->proxy_ca_certs ? d->md->proxy_ca_certs : d->proxy_ca_certs))) { md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p, "create ACME communications"); goto out; diff --git a/modules/md/md_core.c b/modules/md/md_core.c index ac3336671e..704212365e 100644 --- a/modules/md/md_core.c +++ b/modules/md/md_core.c @@ -260,6 +260,7 @@ md_t *md_clone(apr_pool_t *p, const md_t *src) 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->ca_certs) md->ca_certs = apr_pstrdup(p, src->ca_certs); + if (src->proxy_ca_certs) md->proxy_ca_certs = apr_pstrdup(p, src->proxy_ca_certs); 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); } @@ -319,6 +320,7 @@ md_json_t *md_to_json(const md_t *md, apr_pool_t *p) 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_certs) md_json_sets(md->ca_certs, json, MD_KEY_CA_CERTS, NULL); + if (md->proxy_ca_certs) md_json_sets(md->proxy_ca_certs, json, MD_KEY_PROXY_CA_CERTS, 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); @@ -390,6 +392,7 @@ md_t *md_from_json(md_json_t *json, apr_pool_t *p) 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); md->ca_certs = md_json_dups(p, json, MD_KEY_CA_CERTS, NULL); + md->proxy_ca_certs = md_json_dups(p, json, MD_KEY_PROXY_CA_CERTS, 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); diff --git a/modules/md/md_curl.c b/modules/md/md_curl.c index 2484faf51a..ee1c3cabd2 100644 --- a/modules/md/md_curl.c +++ b/modules/md/md_curl.c @@ -248,6 +248,7 @@ static apr_status_t internals_setup(md_http_request_t *req) CURL *curl; apr_status_t rv = APR_SUCCESS; long ssl_options = 0; + long proxy_ssl_options = 0; curl = md_http_get_impl_data(req->http); if (!curl) { @@ -315,6 +316,16 @@ static apr_status_t internals_setup(md_http_request_t *req) ssl_options |= CURLSSLOPT_NO_REVOKE; #endif } + if (req->proxy_ca_file) { + curl_easy_setopt(curl, CURLOPT_PROXY_CAINFO, req->proxy_ca_file); + /* for a custom CA, allow certificates checking to ignore the + * Schannel error CRYPT_E_NO_REVOCATION_CHECK (could be a missing OCSP + * responder URL in the certs???). See issue #361 */ +#ifdef CURLSSLOPT_NO_REVOKE + proxy_ssl_options |= CURLSSLOPT_NO_REVOKE; +#endif + } + if (req->unix_socket_path) { curl_easy_setopt(curl, CURLOPT_UNIX_SOCKET_PATH, req->unix_socket_path); } @@ -356,6 +367,9 @@ static apr_status_t internals_setup(md_http_request_t *req) if (ssl_options) curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, ssl_options); + if (proxy_ssl_options) + curl_easy_setopt(curl, CURLOPT_PROXY_SSL_OPTIONS, proxy_ssl_options); + leave: req->internals = (APR_SUCCESS == rv)? internals : NULL; return rv; diff --git a/modules/md/md_http.c b/modules/md/md_http.c index 11f10a6b1b..4884dcaa66 100644 --- a/modules/md/md_http.c +++ b/modules/md/md_http.c @@ -36,6 +36,7 @@ struct md_http_t { const char *unix_socket_path; md_http_timeouts_t timeout; const char *ca_file; + const char *proxy_ca_file; }; static md_http_impl_t *cur_impl; @@ -107,6 +108,9 @@ apr_status_t md_http_clone(md_http_t **phttp, if (source_http->ca_file) { (*phttp)->ca_file = apr_pstrdup(p, source_http->ca_file); } + if (source_http->proxy_ca_file) { + (*phttp)->proxy_ca_file = apr_pstrdup(p, source_http->proxy_ca_file); + } } return rv; } @@ -163,6 +167,11 @@ void md_http_set_ca_file(md_http_t *http, const char *ca_file) http->ca_file = ca_file; } +void md_http_set_proxy_ca_file(md_http_t *http, const char *ca_file) +{ + http->proxy_ca_file = ca_file; +} + void md_http_set_unix_socket_path(md_http_t *http, const char *path) { http->unix_socket_path = path; @@ -235,6 +244,7 @@ static apr_status_t req_create(md_http_request_t **preq, md_http_t *http, req->proxy_url = http->proxy_url; req->timeout = http->timeout; req->ca_file = http->ca_file; + req->proxy_ca_file = http->proxy_ca_file; req->unix_socket_path = http->unix_socket_path; *preq = req; return rv; diff --git a/modules/md/md_http.h b/modules/md/md_http.h index 2f250f6d76..a777c14983 100644 --- a/modules/md/md_http.h +++ b/modules/md/md_http.h @@ -65,6 +65,7 @@ struct md_http_request_t { const char *user_agent; const char *proxy_url; const char *ca_file; + const char *proxy_ca_file; const char *unix_socket_path; apr_table_t *headers; struct apr_bucket_brigade *body; @@ -119,12 +120,19 @@ void md_http_set_stalling_default(md_http_t *http, long bytes_per_sec, apr_time_ void md_http_set_stalling(md_http_request_t *req, long bytes_per_sec, apr_time_t timeout); /** - * Set a CA file (in PERM format) to use for root certificates when + * Set a CA file (in PEM format) to use for root certificates when * verifying SSL connections. If not set (or set to NULL), the systems * certificate store will be used. */ void md_http_set_ca_file(md_http_t *http, const char *ca_file); +/** + * Set a CA file (in PEM format) to use for root certificates when + * verifying SSL connections to the HTTP proxy. If not set (or set to NULL), + * the systems certificate store will be used. + */ +void md_http_set_proxy_ca_file(md_http_t *http, const char *ca_file); + /** * Set the path of a unix domain socket for use instead of TCP * in a connection. Disable by providing NULL as path. diff --git a/modules/md/md_reg.c b/modules/md/md_reg.c index 5dd0dddf90..c461fc2b61 100644 --- a/modules/md/md_reg.c +++ b/modules/md/md_reg.c @@ -48,6 +48,7 @@ struct md_reg_t { int can_https; const char *proxy_url; const char *ca_certs; + const char *proxy_ca_certs; int domains_frozen; md_timeslice_t *renew_window; md_timeslice_t *warn_window; @@ -97,8 +98,9 @@ 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_certs, - apr_time_t min_delay, int retry_failover, - int use_store_locks, apr_time_t lock_wait_timeout) + const char *proxy_ca_certs, apr_time_t min_delay, + int retry_failover, int use_store_locks, + apr_time_t lock_wait_timeout) { md_reg_t *reg; apr_status_t rv; @@ -113,6 +115,8 @@ apr_status_t md_reg_create(md_reg_t **preg, apr_pool_t *p, struct md_store_t *st reg->proxy_url = apr_pstrdup(p, proxy_url); reg->ca_certs = (ca_certs && apr_cstr_casecmp("none", ca_certs))? apr_pstrdup(p, ca_certs) : NULL; + reg->proxy_ca_certs = (proxy_ca_certs && apr_cstr_casecmp("none", proxy_ca_certs))? + apr_pstrdup(p, proxy_ca_certs) : NULL; reg->min_delay = min_delay; reg->retry_failover = retry_failover; reg->use_store_locks = use_store_locks; @@ -1110,6 +1114,7 @@ static apr_status_t run_init(void *baton, apr_pool_t *p, ...) driver->store = md_reg_store_get(reg); driver->proxy_url = reg->proxy_url; driver->ca_certs = reg->ca_certs; + driver->proxy_ca_certs = reg->proxy_ca_certs; driver->md = md; driver->can_http = reg->can_http; driver->can_https = reg->can_https; diff --git a/modules/md/md_reg.h b/modules/md/md_reg.h index 0299272c2e..452b58ec3d 100644 --- a/modules/md/md_reg.h +++ b/modules/md/md_reg.h @@ -40,13 +40,15 @@ typedef struct md_reg_t md_reg_t; * @param store the store to base on * @param proxy_url optional URL of a proxy to use for requests * @param ca_certs optional CA trust anchor file to use + * @param proxy_ca_certs optional CA trust anchor file to use for the HTTP proxy * @param min_delay minimum delay between renewal attempts for a domain * @param retry_failover number of failed renewals attempt to fail over to alternate ACME ca */ 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_certs, - apr_time_t min_delay, int retry_failover, - int use_store_locks, apr_time_t lock_wait_timeout); + const char *proxy_ca_certs, apr_time_t min_delay, + int retry_failover, int use_store_locks, + apr_time_t lock_wait_timeout); md_store_t *md_reg_store_get(md_reg_t *reg); @@ -225,6 +227,7 @@ struct md_proto_driver_t { md_store_t *store; const char *proxy_url; const char *ca_certs; + const char *proxy_ca_certs; const md_t *md; int can_http; diff --git a/modules/md/mod_md.c b/modules/md/mod_md.c index f8107f44b0..03862c2298 100644 --- a/modules/md/mod_md.c +++ b/modules/md/mod_md.c @@ -857,6 +857,7 @@ static apr_status_t md_post_config_before_ssl(apr_pool_t *p, apr_pool_t *plog, md_store_t *store; const char *proxy_url; const char *ca_certs; + const char *proxy_ca_certs; apr_pool_userdata_get(&data, mod_md_init_key, s->process->pool); if (data == NULL) { @@ -897,8 +898,9 @@ static apr_status_t md_post_config_before_ssl(apr_pool_t *p, apr_pool_t *plog, proxy_url = apr_table_get(mc->env, MD_KEY_PROXY_URL); ca_certs = apr_table_get(mc->env, MD_KEY_CA_CERTS); + proxy_ca_certs = apr_table_get(mc->env, MD_KEY_PROXY_CA_CERTS); - rv = md_reg_create(&mc->reg, p, store, proxy_url, ca_certs, + rv = md_reg_create(&mc->reg, p, store, proxy_url, ca_certs, proxy_ca_certs, mc->min_delay, mc->retry_failover, mc->use_store_locks, mc->lock_wait_timeout); if (APR_SUCCESS != rv) { diff --git a/modules/md/mod_md_config.c b/modules/md/mod_md_config.c index 7f109781cc..a691b1d016 100644 --- a/modules/md/mod_md_config.c +++ b/modules/md/mod_md_config.c @@ -127,6 +127,7 @@ static md_srv_conf_t defconf = { NULL, /* dns01_cmd */ NULL, /* proxy URL */ NULL, /* CA cert file to use */ + NULL, /* CA cert file to use for proxy */ NULL, /* currently defined md */ NULL, /* assigned md, post config */ 0, /* is_ssl, set during mod_ssl post_config */ @@ -187,6 +188,7 @@ static void srv_conf_props_clear(md_srv_conf_t *sc) sc->dns01_cmd = NULL; sc->proxy_url = NULL; sc->ca_certs = NULL; + sc->proxy_ca_certs = NULL; } static void srv_conf_props_copy(md_srv_conf_t *to, const md_srv_conf_t *from) @@ -213,6 +215,7 @@ static void srv_conf_props_copy(md_srv_conf_t *to, const md_srv_conf_t *from) to->dns01_cmd = from->dns01_cmd; to->proxy_url = from->proxy_url; to->ca_certs = from->ca_certs; + to->proxy_ca_certs = from->proxy_ca_certs; } static void srv_conf_props_apply(md_t *md, const md_srv_conf_t *from, apr_pool_t *p) @@ -242,6 +245,7 @@ static void srv_conf_props_apply(md_t *md, const md_srv_conf_t *from, apr_pool_t if (from->dns01_cmd) md->dns01_cmd = from->dns01_cmd; if (from->proxy_url) md->proxy_url = from->proxy_url; if (from->ca_certs) md->ca_certs = from->ca_certs; + if (from->proxy_ca_certs) md->proxy_ca_certs = from->proxy_ca_certs; } void *md_config_create_svr(apr_pool_t *pool, server_rec *s) @@ -293,6 +297,7 @@ static void *md_config_merge(apr_pool_t *pool, void *basev, void *addv) 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->ca_certs = (add->ca_certs)? add->ca_certs : base->ca_certs; + nsc->proxy_ca_certs = (add->proxy_ca_certs)? add->proxy_ca_certs : base->proxy_ca_certs; nsc->current = NULL; return nsc; @@ -1273,6 +1278,25 @@ static const char *md_config_set_ca_certs(cmd_parms *cmd, void *arg, const char return NULL; } +static const char *md_config_set_proxy_ca_certs(cmd_parms *cmd, void *arg, const char *value) +{ + md_srv_conf_t *sc = md_config_get(cmd->server); + const char *err; + + if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) { + return err; + } + + if (inside_md_section(cmd)) { + sc->proxy_ca_certs = value; + } else { + apr_table_set(sc->mc->env, MD_KEY_PROXY_CA_CERTS, value); + } + + (void)arg; + return NULL; +} + static const char *md_config_set_eab(cmd_parms *cmd, void *dc, const char *keyid, const char *hmac) { @@ -1411,6 +1435,8 @@ const command_rec md_cmds[] = { "How long to delay activation of new certificates"), AP_INIT_TAKE1("MDCACertificateFile", md_config_set_ca_certs, NULL, RSRC_CONF, "Set the CA file to use for connections"), + AP_INIT_TAKE1("MDHttpProxyCACertificateFile", md_config_set_proxy_ca_certs, NULL, RSRC_CONF, + "Set the CA file to use for connections to the HTTP(S) proxy"), 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, diff --git a/modules/md/mod_md_config.h b/modules/md/mod_md_config.h index 8e685f08db..3bff6c4c7d 100644 --- a/modules/md/mod_md_config.h +++ b/modules/md/mod_md_config.h @@ -115,6 +115,8 @@ typedef struct md_srv_conf_t { const char *proxy_url; /* Proxy URL, override global command */ const char *ca_certs; /* root certificates to use for connections, override global command */ + const char *proxy_ca_certs; /* root certificates to use for proxy connections, + override global command */ md_t *current; /* md currently defined in section */ struct apr_array_header_t *assigned; /* post_config: MDs that apply to this server */