From: Stefan Eissing Date: Mon, 4 Sep 2017 14:07:29 +0000 (+0000) Subject: On the trunk: X-Git-Tag: 2.5.0-alpha~170 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1b3a51df34426ba789d9ada42cb493b89cfb62c5;p=thirdparty%2Fapache%2Fhttpd.git On the trunk: mod_md: * Improved interface to mod_ssl for fallback handling. Backward compatible to previous mod_ssl patch, but fallbacks will not work. * Provide a temporary, self-signed certificate with a speaking command and domain name if we have no other cert for a Managed Domain, yet. Refs github issue #32 * Continue to provide expired or not-completely matching, existing certificate for a Managed Domain until the renewal was successful. This is helpful when one adds a DNS name to a MD, so the previous domains can be served while a new cert is requested. * All files necessary to run tests are not in the release package. * Making "http-01" the preferred challenge type again, as people "tls-sni-01" requires at least one working certificate vhost right now - which not everyone has. * moved part of the MD sanity checks from post_config to check_config phase, allowing for error detection in check-only runs. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1807228 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/CHANGES b/CHANGES index e2133fab550..bb8a953107f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,39 +1,9 @@ -*- coding: utf-8 -*- Changes with Apache 2.5.0 - *) mod_md: v0.8.1: - - New directive ```MDPrivateKeys``` to specify the type and parameter to private key generation. - Currently only 'RSA' is supported as type with an option number of bits >= 2048 as parameter. - Simple test cases for config handling added. - - Private RSA keys are now generated with 2048 bits by default. Use ```MDPrivateKeys``` for - higher security. - - IMPORTANT: store format change. The following changes will be made to an existing md store on - first start with a new version (be it by mod_md in the server or a run by a new 'a2md'): - - pkey.pem will be renamed to privkey.pem - - cert.pem and chain.pem will be concatenated to pubcert.pem. The former files will remain, - but no longer be used. They will disappear on next renewal. - ADVICE: If the current store data is vital to you, please make a backup first! - - Fixed test case clearing of store to keep key alive, enabling true random store key again. - - Removed pun "Something, like certbot" from the User-Agent request header. Refs issue #34 - - Cleaned up reporting of missing/mismatched MDCertificateAgreement in the logs. This will - no longer trigger early retries. - - badNonce encounters are no longer reported as errors. Retries are attempted now silently. - Refs github issue #35 - - new default MDRenewWindow. Instead of 14 days, the default is now a third before the end of - the certificates lifetime. For the usual 90 days of Let's Encrypt certificates, this makes - an effective renewal window of 30 days - as recommended by LE. Refs issue #30 - - Enabled conversion warnings if supported by compiler, eliminated several signed/unsigned - warnings. - - LIVE: the real Let's Encrypt CA is now live by default! If you need to experiment, configure - MDCertificateAuthority https://acme-staging.api.letsencrypt.org/directory - - When existing, complete certificates are renewed, the activation of the new ones is - delayed by 24 hours (or until the existing ones expire, whatever is earler) to accomodate - for clients with weird clocks, refs #1. - - Fixed store sync when MDCAChallenges was removed again from an MD. - - Fixed crash when MD matched the base server, fixes #23 - - Fixed watchgod resetting staging when server processes disappeared (e.g. reached - max requests or other limits). - [Stefan Eissing] + *) mod_md: v0.9.0: + Certificate provisioning from Let's Encrypt (and other ACME CAs) for mod_ssl virtual hosts. + [Stefan Eissing] *) mod_proxy: loadfactor parameter can now be a decimal number (eg: 1.25). [Jim Jagielski] diff --git a/modules/md/md.h b/modules/md/md.h index f723309ba58..fb33460831b 100644 --- a/modules/md/md.h +++ b/modules/md/md.h @@ -143,6 +143,9 @@ struct md_t { #define MD_FN_CHAIN "chain.pem" #define MD_FN_HTTPD_JSON "httpd.json" +#define MD_FN_FALLBACK_PKEY "fallback-privkey.pem" +#define MD_FN_FALLBACK_CERT "fallback-cert.pem" + /* Check if a string member of a new MD (n) has * a value and if it differs from the old MD o */ diff --git a/modules/md/md_acme_drive.c b/modules/md/md_acme_drive.c index 068c0d33491..bf31e4b6e92 100644 --- a/modules/md/md_acme_drive.c +++ b/modules/md/md_acme_drive.c @@ -617,8 +617,8 @@ static apr_status_t acme_driver_init(md_proto_driver_t *d) } else { /* free to chose. Add all we support and see what we get offered */ - APR_ARRAY_PUSH(ad->ca_challenges, const char*) = MD_AUTHZ_TYPE_TLSSNI01; APR_ARRAY_PUSH(ad->ca_challenges, const char*) = MD_AUTHZ_TYPE_HTTP01; + APR_ARRAY_PUSH(ad->ca_challenges, const char*) = MD_AUTHZ_TYPE_TLSSNI01; } if (!d->can_http && !d->can_https) { diff --git a/modules/md/md_reg.c b/modules/md/md_reg.c index 25a841850f6..96a294be7c3 100644 --- a/modules/md/md_reg.c +++ b/modules/md/md_reg.c @@ -424,12 +424,10 @@ md_t *md_reg_find_overlap(md_reg_t *reg, const md_t *md, const char **pdomain, a } apr_status_t md_reg_get_cred_files(md_reg_t *reg, const md_t *md, apr_pool_t *p, - const char **pkeyfile, const char **pcertfile, - const char **pchainfile) + const char **pkeyfile, const char **pcertfile) { apr_status_t rv; - *pchainfile = NULL; rv = md_store_get_fname(pkeyfile, reg->store, MD_SG_DOMAINS, md->name, MD_FN_PRIVKEY, p); if (APR_SUCCESS == rv) { rv = md_store_get_fname(pcertfile, reg->store, MD_SG_DOMAINS, md->name, MD_FN_PUBCERT, p); diff --git a/modules/md/md_reg.h b/modules/md/md_reg.h index 285ffc7ee1b..1ab0dc0fd58 100644 --- a/modules/md/md_reg.h +++ b/modules/md/md_reg.h @@ -108,8 +108,7 @@ apr_status_t md_reg_creds_get(const md_creds_t **pcreds, md_reg_t *reg, md_store_group_t group, const md_t *md, apr_pool_t *p); apr_status_t md_reg_get_cred_files(md_reg_t *reg, const md_t *md, apr_pool_t *p, - const char **pkeyfile, const char **pcertfile, - const char **pchainfile); + const char **pkeyfile, const char **pcertfile); /** * Synchronise the give master mds with the store. diff --git a/modules/md/md_store_fs.c b/modules/md/md_store_fs.c index d37968a5222..49f73945850 100644 --- a/modules/md/md_store_fs.c +++ b/modules/md/md_store_fs.c @@ -258,6 +258,57 @@ read: return rv; } +static apr_status_t setup_fallback_cert(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap) +{ + md_store_fs_t *s_fs = baton; + md_pkey_t *fallback_key; + md_cert_t *fallback_cert; + md_pkey_spec_t spec; + apr_status_t rv; + + if (APR_SUCCESS == (rv = fs_load(&s_fs->s, MD_SG_NONE, NULL, MD_FN_FALLBACK_PKEY, + MD_SV_PKEY, (void**)&fallback_key, ptemp)) + && APR_SUCCESS == (rv = fs_load(&s_fs->s, MD_SG_NONE, NULL, MD_FN_FALLBACK_CERT, + MD_SV_CERT, (void**)&fallback_cert, ptemp))) { + apr_time_t not_after = md_cert_get_not_after(fallback_cert); + if (not_after > apr_time_now() + apr_time_from_sec(7 * MD_SECS_PER_DAY)) { + /* at least a week more valid, expect drive and restart way before that */ + return APR_SUCCESS; + } + } + + spec.type = MD_PKEY_TYPE_RSA; + spec.params.rsa.bits = MD_PKEY_RSA_BITS_DEF; + + if (APR_SUCCESS != (rv = md_pkey_gen(&fallback_key, ptemp, &spec))) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ptemp, "create fallback key"); + return rv; + } + + if (APR_SUCCESS != (rv = md_store_save(&s_fs->s, ptemp, MD_SG_NONE, NULL, + MD_FN_FALLBACK_PKEY, MD_SV_PKEY, + (void*)fallback_key, 0))) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ptemp, "save fallback key"); + return rv; + } + + if (APR_SUCCESS != (rv = md_cert_self_sign(&fallback_cert, "Apache Managed Domain Fallback", + "temporary.invalid.certificate", fallback_key, + apr_time_from_sec(14 * MD_SECS_PER_DAY), ptemp))) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ptemp, "create fallback certificate"); + return rv; + } + + if (APR_SUCCESS != (rv = md_store_save(&s_fs->s, ptemp, MD_SG_NONE, NULL, + MD_FN_FALLBACK_CERT, MD_SV_CERT, + (void*)fallback_cert, 0))) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ptemp, "save fallback certificate"); + return rv; + } + + return rv; +} + apr_status_t md_store_fs_init(md_store_t **pstore, apr_pool_t *p, const char *path) { md_store_fs_t *s_fs; @@ -302,6 +353,9 @@ apr_status_t md_store_fs_init(md_store_t **pstore, apr_pool_t *p, const char *pa } } rv = md_util_pool_vdo(setup_store_file, s_fs, p, NULL); + if (APR_SUCCESS == rv) { + rv = md_util_pool_vdo(setup_fallback_cert, s_fs, p, NULL); + } if (APR_SUCCESS != rv) { md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "init fs store at %s", path); diff --git a/modules/md/md_version.h b/modules/md/md_version.h index 306f40fec7d..5ef47d426b9 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.8.1" +#define MOD_MD_VERSION "0.9.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 0x000801 +#define MOD_MD_VERSION_NUM 0x000900 #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 44025e4c8c5..4d8721651b2 100644 --- a/modules/md/mod_md.c +++ b/modules/md/mod_md.c @@ -741,18 +741,11 @@ static void load_stage_sets(apr_array_header_t *names, apr_pool_t *p, return; } -static apr_status_t md_post_config(apr_pool_t *p, apr_pool_t *plog, - apr_pool_t *ptemp, server_rec *s) +static apr_status_t md_check_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) { const char *mod_md_init_key = "mod_md_init_counter"; void *data = NULL; - md_srv_conf_t *sc; - md_mod_conf_t *mc; - apr_array_header_t *drive_names; - md_reg_t *reg; - apr_status_t rv = APR_SUCCESS; - const md_t *md; - int i; apr_pool_userdata_get(&data, mod_md_init_key, s->process->pool); if (data == NULL) { @@ -767,20 +760,29 @@ static apr_status_t md_post_config(apr_pool_t *p, apr_pool_t *plog, } init_setups(p, s); - md_log_set(log_is_level, log_print, NULL); - sc = md_config_get(s); - mc = sc->mc; - - /* 1. Check uniqueness of MDs, calculate global, configured MD list. + /* Check uniqueness of MDs, calculate global, configured MD list. * If successful, we have a list of MD definitions that do not overlap. */ /* We also need to find out if we can be reached on 80/443 from the outside (e.g. the CA) */ - if (APR_SUCCESS != (rv = md_calc_md_list(p, plog, ptemp, s))) { - goto out; - } + return md_calc_md_list(p, plog, ptemp, s); +} - /* 2. Synchronize the defintions we now have with the store via a registry (reg). */ +static apr_status_t md_post_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + md_srv_conf_t *sc; + md_mod_conf_t *mc; + md_reg_t *reg; + const md_t *md; + apr_array_header_t *drive_names; + apr_status_t rv = APR_SUCCESS; + int i; + + sc = md_config_get(s); + mc = sc->mc; + + /* Synchronize the defintions we now have with the store via a registry (reg). */ if (APR_SUCCESS != (rv = setup_reg(®, p, s, 1))) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10072) "setup md registry"); @@ -790,10 +792,9 @@ static apr_status_t md_post_config(apr_pool_t *p, apr_pool_t *plog, mc->can_http, mc->can_https))) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10073) "synching %d mds to registry", mc->mds->nelts); - goto out; } - /* 3. Determine the managed domains that are in auto drive_mode. For those, + /* Determine the managed domains that are in auto drive_mode. For those, * determine in which state they are: * - UNKNOWN: should not happen, report, dont drive * - ERROR: something we do not know how to fix, report, dont drive @@ -820,7 +821,7 @@ static apr_status_t md_post_config(apr_pool_t *p, apr_pool_t *plog, } } - /* 4. I there are MDs to drive, start a watchdog to check on them regularly */ + /* If there are MDs to drive, start a watchdog to check on them regularly */ if (drive_names->nelts > 0) { ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(10074) "%d out of %d mds are configured for auto-drive", @@ -834,7 +835,7 @@ static apr_status_t md_post_config(apr_pool_t *p, apr_pool_t *plog, ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10075) "no mds to auto drive, no watchdog needed"); } -out: +out: return rv; } @@ -855,9 +856,8 @@ static int md_is_managed(server_rec *s) return 0; } -static apr_status_t md_get_credentials(server_rec *s, apr_pool_t *p, - const char **pkeyfile, const char **pcertfile, - const char **pchainfile) +static apr_status_t md_get_certificate(server_rec *s, apr_pool_t *p, + const char **pkeyfile, const char **pcertfile) { apr_status_t rv = APR_ENOENT; md_srv_conf_t *sc; @@ -866,26 +866,53 @@ static apr_status_t md_get_credentials(server_rec *s, apr_pool_t *p, *pkeyfile = NULL; *pcertfile = NULL; - *pchainfile = NULL; sc = md_config_get(s); if (sc && sc->assigned) { assert(sc->mc); assert(sc->mc->store); - if (APR_SUCCESS == (rv = md_reg_init(®, p, sc->mc->store))) { - md = md_reg_get(reg, sc->assigned->name, p); - if (md->state != MD_S_COMPLETE) { - return APR_EAGAIN; - } - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10077) - "%s: loading credentials for server %s", md->name, s->server_hostname); - return md_reg_get_cred_files(reg, md, p, pkeyfile, pcertfile, pchainfile); + if (APR_SUCCESS != (rv = md_reg_init(®, p, sc->mc->store))) { + return rv; + } + + md = md_reg_get(reg, sc->assigned->name, p); + + if (APR_SUCCESS != (rv = md_reg_get_cred_files(reg, md, p, pkeyfile, pcertfile))) { + return rv; + } + + if (!*pkeyfile || !*pcertfile + || APR_SUCCESS != md_util_is_file(*pkeyfile, p) + || APR_SUCCESS != md_util_is_file(*pcertfile, p)) { + /* Provide temporary, self-signed certificate as fallback, so that + * clients do not get obscure TLS handshake errors or will see a fallback + * virtual host that is not intended to be served here. */ + md_store_get_fname(pkeyfile, sc->mc->store, MD_SG_NONE, NULL, MD_FN_FALLBACK_PKEY, p); + md_store_get_fname(pcertfile, sc->mc->store, MD_SG_NONE, NULL, MD_FN_FALLBACK_CERT, p); + + return APR_EAGAIN; + } + + /* We have key and cert files, but they might no longer be valid or not + * match all domain names. Still use these files for now, but indicate that + * resources should no longer be served until we have a new certificate again. */ + if (md->state != MD_S_COMPLETE) { + return APR_EAGAIN; } + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10077) + "%s: providing certificate for server %s", md->name, s->server_hostname); } return rv; } +static apr_status_t md_get_credentials(server_rec *s, apr_pool_t *p, + const char **pkeyfile, const char **pcertfile, + const char **pchainfile) +{ + *pchainfile = NULL; + return md_get_certificate(s, p, pkeyfile, pcertfile); +} static int md_is_challenge(conn_rec *c, const char *servername, X509 **pcert, EVP_PKEY **pkey) @@ -1005,6 +1032,7 @@ static void md_hooks(apr_pool_t *pool) /* Run once after configuration is set, before mod_ssl. */ + ap_hook_check_config(md_check_config, NULL, mod_ssl, APR_HOOK_MIDDLE); ap_hook_post_config(md_post_config, NULL, mod_ssl, APR_HOOK_MIDDLE); /* Run once after a child process has been created. @@ -1015,6 +1043,7 @@ static void md_hooks(apr_pool_t *pool) ap_hook_post_read_request(md_http_challenge_pr, NULL, NULL, APR_HOOK_MIDDLE); APR_REGISTER_OPTIONAL_FN(md_is_managed); + APR_REGISTER_OPTIONAL_FN(md_get_certificate); APR_REGISTER_OPTIONAL_FN(md_get_credentials); APR_REGISTER_OPTIONAL_FN(md_is_challenge); } diff --git a/modules/md/mod_md.h b/modules/md/mod_md.h index 40e4cd2c011..860616808bd 100644 --- a/modules/md/mod_md.h +++ b/modules/md/mod_md.h @@ -24,6 +24,17 @@ struct server_rec; APR_DECLARE_OPTIONAL_FN(int, md_is_managed, (struct server_rec *)); +/** + * Get the certificate/key for the managed domain (md_is_managed != 0). + * + * @return APR_EAGAIN if the real certicate is not available yet + */ +APR_DECLARE_OPTIONAL_FN(apr_status_t, + md_get_certificate, (struct server_rec *, apr_pool_t *, + const char **pkeyfile, + const char **pcertfile)); + +/* previous version for md_get_certificate, to be phased out soon */ APR_DECLARE_OPTIONAL_FN(apr_status_t, md_get_credentials, (struct server_rec *, apr_pool_t *, const char **pkeyfile,