From 4596674481dbbeb9026f0ff793699479e1cfb09d Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Tue, 5 Dec 2017 09:22:34 -0500 Subject: [PATCH] Enable upgrades from a mod_nss-installed master to mod_ssl The existing private/public keys are migrated to PEM files via a PKCS#12 temporary file. This should work for both IPA-generated and user-provided server certificates. Related: https://pagure.io/freeipa/issue/3757 Reviewed-By: Christian Heimes Reviewed-By: Stanislav Laznicka Reviewed-By: Rob Crittenden --- ipaserver/install/httpinstance.py | 83 ++++++++++++++++++++++++++--- ipaserver/install/server/upgrade.py | 64 +++++----------------- 2 files changed, 88 insertions(+), 59 deletions(-) diff --git a/ipaserver/install/httpinstance.py b/ipaserver/install/httpinstance.py index 9c6c7ec1f..61a88e2b8 100644 --- a/ipaserver/install/httpinstance.py +++ b/ipaserver/install/httpinstance.py @@ -116,6 +116,8 @@ class HTTPInstance(service.Service): self.step("stopping httpd", self.__stop) self.step("backing up ssl.conf", self.backup_ssl_conf) self.step("disabling nss.conf", self.disable_nss_conf) + self.step("configuring mod_ssl certificate paths", + self.configure_mod_ssl_certs) self.step("setting mod_ssl protocol list to TLSv1.0 - TLSv1.2", self.set_mod_ssl_protocol) self.step("configuring mod_ssl log directory", @@ -198,6 +200,11 @@ class HTTPInstance(service.Service): tasks.configure_http_gssproxy_conf(IPAAPI_USER) services.knownservices.gssproxy.restart() + def get_mod_nss_nickname(self): + cert = installutils.get_directive(paths.HTTPD_NSS_CONF, 'NSSNickname') + nickname = installutils.unquote_directive_value(cert, quote_char="'") + return nickname + def backup_ssl_conf(self): self.fstore.backup_file(paths.HTTPD_SSL_CONF) @@ -225,7 +232,6 @@ class HTTPInstance(service.Service): if sysupgrade.get_upgrade_state('http', OCSP_ENABLED) is None: self.__disable_mod_ssl_ocsp() sysupgrade.set_upgrade_state('http', OCSP_ENABLED, False) ->>>>>>> 754c26ef6... Revisions for ocsp def __disable_mod_ssl_ocsp(self): aug = Augeas(flags=Augeas.NO_LOAD | Augeas.NO_MODL_AUTOLOAD) @@ -250,6 +256,35 @@ class HTTPInstance(service.Service): aug.set(ocsp_comment, '{} {}'.format(OCSP_DIRECTIVE, ocsp_state)) aug.save() +# def disable_mod_nss_ocsp(self): +# if sysupgrade.get_upgrade_state('http', NSS_OCSP_ENABLED) is None: +# self.__disable_mod_nss_ocsp() +# sysupgrade.set_upgrade_state('http', NSS_OCSP_ENABLED, False) + +# def __disable_mod_nss_ocsp(self): +# aug = Augeas(flags=Augeas.NO_LOAD | Augeas.NO_MODL_AUTOLOAD) +# +# aug.set('/augeas/load/Httpd/lens', 'Httpd.lns') +# aug.set('/augeas/load/Httpd/incl', paths.HTTPD_NSS_CONF) +# aug.load() +# +# path = '/files{}/VirtualHost'.format(paths.HTTPD_NSS_CONF) +# ocsp_path = '{}/directive[.="{}"]'.format(path, OCSP_DIRECTIVE) +# ocsp_arg = '{}/arg'.format(ocsp_path) +# ocsp_comment = '{}/#comment[.="{}"]'.format(path, OCSP_DIRECTIVE) +# +# ocsp_dir = aug.get(ocsp_path) +# +# # there is NSSOCSP directive in nss.conf file, comment it +# # otherwise just do nothing +# if ocsp_dir is not None: +# ocsp_state = aug.get(ocsp_arg) +# aug.remove(ocsp_arg) +# aug.rename(ocsp_path, '#comment') +# aug.set(ocsp_comment, '{} {}'.format(OCSP_DIRECTIVE, ocsp_state)) +# aug.save() + + def __add_include(self): """This should run after __set_mod_nss_port so is already backed up""" if installutils.update_file(paths.HTTPD_SSL_CONF, @@ -347,9 +382,9 @@ class HTTPInstance(service.Service): finally: if prev_helper is not None: certmonger.modify_ca_helper('IPA', prev_helper) - self.cert = x509.load_der_x509_certificate( - paths.HTTPD_CERT_FILE - ) + self.cert = x509.load_certificate_from_file( + paths.HTTPD_CERT_FILE + ) if prev_helper is not None: self.add_cert_to_service() @@ -363,7 +398,10 @@ class HTTPInstance(service.Service): # store the CA cert nickname so that we can publish it later on # self.cacert_nickname = db.cacert_name # FIXME: figure this out too + sysupgrade.set_upgrade_state('ssl.conf', 'migrated_to_mod_ssl', True) + def configure_mod_ssl_certs(self): + """Configure the mod_ssl certificate directives""" installutils.set_directive(paths.HTTPD_SSL_CONF, 'SSLCertificateFile', paths.HTTPD_CERT_FILE, False) @@ -499,14 +537,14 @@ class HTTPInstance(service.Service): str(e)) def start_tracking_certificates(self): - cert = x509.load_pem_x509_certificate(paths.HTTPD_CERT_FILE) + cert = x509.load_certificate_from_file(paths.HTTPD_CERT_FILE) if certs.is_ipa_issued_cert(api, cert): request_id = certmonger.start_tracking( - certpath=(paths.HTTPD_CERT_FILE, paths.HTTPD_CERT_KEY), - post_command='restart_httpd' + certpath=(paths.HTTPD_CERT_FILE, paths.HTTPD_KEY_FILE), + post_command='restart_httpd', storage='FILE' ) subject = str(DN(cert.subject)) - certmonger.add_principal(request_id, principal) + certmonger.add_principal(request_id, self.principal) certmonger.add_subject(request_id, subject) else: logger.debug("Will not track HTTP server cert %s as it is not " @@ -530,3 +568,32 @@ class HTTPInstance(service.Service): remote_ldap.simple_bind(ipaldap.DIRMAN_DN, self.dm_password) replication.wait_for_entry(remote_ldap, service_dn, timeout=60) + + def migrate_to_mod_ssl(self): + """For upgrades only, migrate from mod_nss to mod_ssl""" + db = certs.CertDB(api.env.realm, nssdir=paths.HTTPD_ALIAS_DIR) + nickname = self.get_mod_nss_nickname() + with tempfile.NamedTemporaryFile() as temp: + pk12_password = ipautil.ipa_generate_password() + pk12_pwdfile = ipautil.write_tmp_file(pk12_password) + db.export_pkcs12(temp.name, pk12_pwdfile.name, nickname) + certs.install_pem_from_p12(temp.name, + pk12_password, + paths.HTTPD_CERT_FILE) + certs.install_key_from_p12(temp.name, + pk12_password, + paths.HTTPD_KEY_FILE) + + self.configure_mod_ssl_certs() + self.set_mod_ssl_protocol() + self.set_mod_ssl_logdir() + + self.cert = x509.load_certificate_from_file(paths.HTTPD_CERT_FILE) + + if self.ca_is_configured: + db.untrack_server_cert(nickname) + self.start_tracking_certificates() + + # remove nickname and CA certs from NSS db + + self.disable_nss_conf() diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py index 5d4fd1725..57fddc6e2 100644 --- a/ipaserver/install/server/upgrade.py +++ b/ipaserver/install/server/upgrade.py @@ -13,7 +13,7 @@ import fileinput import sys from augeas import Augeas import dns.exception -from ipalib import api +from ipalib import api, x509 from ipalib.install import certmonger, sysrestore import SSSDConfig import ipalib.util @@ -58,6 +58,7 @@ else: if six.PY3: unicode = str + logger = logging.getLogger(__name__) @@ -987,13 +988,12 @@ def certificate_renewal_update(ca, ds, http): return False # Check the http server cert if issued by IPA - http_nickname = http.get_mod_nss_nickname() - http_db = certs.CertDB(api.env.realm, nssdir=paths.HTTPD_ALIAS_DIR) - if http_db.is_ipa_issued_cert(api, http_nickname): + cert = x509.load_certificate_from_file(paths.HTTPD_CERT_FILE) + if certs.is_ipa_issued_cert(api, cert): requests.append( { - 'cert-database': paths.HTTPD_ALIAS_DIR, - 'cert-nickname': http_nickname, + 'cert-file': paths.HTTPD_CERT_FILE, + 'key-storage': paths.HTTPD_KEY_FILE, 'ca-name': 'IPA', 'cert-postsave-command': template % 'restart_httpd', } @@ -1436,38 +1436,18 @@ def fix_trust_flags(): sysupgrade.set_upgrade_state('http', 'fix_trust_flags', True) -def update_mod_nss_protocol(http): - logger.info('[Updating mod_nss protocol versions]') +def migrate_to_mod_ssl(http): + logger.info('[Migrating from mod_nss to mod_ssl]') - if sysupgrade.get_upgrade_state('nss.conf', 'protocol_updated_tls12'): - logger.info("Protocol versions already updated") + if sysupgrade.get_upgrade_state('ssl.conf', 'migrated_to_mod_ssl'): + logger.info("Already migrated to mod_ssl") return - http.set_mod_nss_protocol() + http.migrate_to_mod_ssl() - sysupgrade.set_upgrade_state('nss.conf', 'protocol_updated_tls12', True) + sysupgrade.set_upgrade_state('ssl.conf', 'migrated_to_mod_ssl', True) -def disable_mod_nss_ocsp(http): - logger.info('[Updating mod_nss enabling OCSP]') - http.disable_mod_nss_ocsp() - - -def update_mod_nss_cipher_suite(http): - logger.info('[Updating mod_nss cipher suite]') - - revision = sysupgrade.get_upgrade_state('nss.conf', 'cipher_suite_updated') - if revision and revision >= httpinstance.NSS_CIPHER_REVISION: - logger.debug("Cipher suite already updated") - return - - http.set_mod_nss_cipher_suite() - - sysupgrade.set_upgrade_state( - 'nss.conf', - 'cipher_suite_updated', - httpinstance.NSS_CIPHER_REVISION) - def update_ipa_httpd_service_conf(http): logger.info('[Updating HTTPD service IPA configuration]') @@ -1628,21 +1608,6 @@ def enable_certauth(krb): aug.close() -def disable_httpd_system_trust(http): - ca_certs = [] - - db = certs.CertDB(api.env.realm, nssdir=paths.HTTPD_ALIAS_DIR) - for nickname, trust_flags in db.list_certs(): - if not trust_flags.has_key: - cert = db.get_cert_from_db(nickname) - if cert: - ca_certs.append((cert, nickname, trust_flags)) - - if http.disable_system_trust(): - for cert, nickname, trust_flags in ca_certs: - db.add_cert(cert, nickname, trust_flags) - - def upgrade_configuration(): """ Execute configuration upgrade of the IPA services @@ -1797,12 +1762,9 @@ def upgrade_configuration(): http.enable_kdcproxy() http.stop() - disable_httpd_system_trust(http) update_ipa_httpd_service_conf(http) update_ipa_http_wsgi_conf(http) - update_mod_nss_protocol(http) - update_mod_nss_cipher_suite(http) - disable_mod_nss_ocsp(http) + migrate_to_mod_ssl(http) fix_trust_flags() update_http_keytab(http) http.configure_gssproxy()