diff --git a/freeipa.spec.in b/freeipa.spec.in index 35a078cd5..b287d28ff 100644 --- a/freeipa.spec.in +++ b/freeipa.spec.in @@ -1307,6 +1307,7 @@ fi %{_libexecdir}/ipa/ipa-custodia %{_libexecdir}/ipa/ipa-custodia-check %{_libexecdir}/ipa/ipa-httpd-kdcproxy +%{_libexecdir}/ipa/ipa-httpd-pwdreader %{_libexecdir}/ipa/ipa-pki-retrieve-key %{_libexecdir}/ipa/ipa-otpd %dir %{_libexecdir}/ipa/oddjob @@ -1458,6 +1459,8 @@ fi %attr(700,root,root) %dir %{_localstatedir}/lib/ipa/sysupgrade %attr(755,root,root) %dir %{_localstatedir}/lib/ipa/pki-ca %attr(755,root,root) %dir %{_localstatedir}/lib/ipa/certs +%attr(700,root,root) %dir %{_localstatedir}/lib/ipa/private +%attr(700,root,root) %dir %{_localstatedir}/lib/ipa/passwds %ghost %{_localstatedir}/lib/ipa/pki-ca/publish %ghost %{_localstatedir}/named/dyndb-ldap/ipa %dir %attr(0700,root,root) %{_sysconfdir}/ipa/custodia diff --git a/install/Makefile.am b/install/Makefile.am index 5d10e270b..caff1be57 100644 --- a/install/Makefile.am +++ b/install/Makefile.am @@ -25,11 +25,15 @@ install-exec-local: $(INSTALL) -d -m 700 $(DESTDIR)$(localstatedir)/lib/ipa/sysupgrade $(INSTALL) -d -m 755 $(DESTDIR)$(localstatedir)/lib/ipa/pki-ca $(INSTALL) -d -m 755 $(DESTDIR)$(localstatedir)/lib/ipa/certs + $(INSTALL) -d -m 700 $(DESTDIR)$(localstatedir)/lib/ipa/private + $(INSTALL) -d -m 700 $(DESTDIR)$(localstatedir)/lib/ipa/passwds uninstall-local: -rmdir $(DESTDIR)$(localstatedir)/lib/ipa/sysrestore -rmdir $(DESTDIR)$(localstatedir)/lib/ipa/sysupgrade -rmdir $(DESTDIR)$(localstatedir)/lib/ipa/certs + -rmdir $(DESTDIR)$(localstatedir)/lib/ipa/private + -rmdir $(DESTDIR)$(localstatedir)/lib/ipa/passwds -rmdir $(DESTDIR)$(localstatedir)/lib/ipa EXTRA_DIST = README.schema diff --git a/install/tools/Makefile.am b/install/tools/Makefile.am index 6b9a64a3d..599519f79 100644 --- a/install/tools/Makefile.am +++ b/install/tools/Makefile.am @@ -36,5 +36,6 @@ dist_app_SCRIPTS = \ ipa-custodia \ ipa-custodia-check \ ipa-httpd-kdcproxy \ + ipa-httpd-pwdreader \ ipa-pki-retrieve-key \ $(NULL) diff --git a/install/tools/ipa-httpd-pwdreader b/install/tools/ipa-httpd-pwdreader new file mode 100755 index 000000000..20f43d9b9 --- /dev/null +++ b/install/tools/ipa-httpd-pwdreader @@ -0,0 +1,36 @@ +#!/bin/bash +# This program is a handler written for Apache mod_ssl's SSLPassPhraseDialog. +# +# If you'd like to write your custom binary providing passwords to mod_ssl, +# see the documentation of the aforementioned directive of the mod_ssl module. + +USAGE="./ipa-pwdreader host:port RSA|DSA|ECC|number" +ERR_UNKNOWN_KEY="\ +ERROR: You seem to be running a non-standard IPA installation. +Please extend the /var/libexec/ipa/ipa-pwdreader script to cover your case." + +if [ ! "$#" -eq 2 ]; then + echo "Wrong number of arguments!" 1>&2 + echo "$USAGE" 1>&2 + exit 1 +fi + + +case "$1" in + "${HOSTNAME}:443" ) + # Read IPA password + # IPA expects the password filename format to be + # -- + IPA_PASSWD_PATH="/var/lib/ipa/passwds/${1/:/-}-$2" + cat $IPA_PASSWD_PATH + ;; +# ================ +# Extend for more virtual hosts with +# : ) +# your_code +# ;; +# ================ + *) + echo "$ERR_UNKNOWN_KEY" 1>&2 + exit 1 +esac diff --git a/ipalib/constants.py b/ipalib/constants.py index e161d65ad..891623089 100644 --- a/ipalib/constants.py +++ b/ipalib/constants.py @@ -319,3 +319,4 @@ USER_CACHE_PATH = ( ) SOFTHSM_DNSSEC_TOKEN_LABEL = u'ipaDNSSEC' +HTTPD_PASSWD_FILE_FMT = "{host}-443-RSA" diff --git a/ipalib/x509.py b/ipalib/x509.py index b49bc9662..7986ddbf5 100644 --- a/ipalib/x509.py +++ b/ipalib/x509.py @@ -569,20 +569,26 @@ def write_certificate_list(certs, filename): raise errors.FileError(reason=str(e)) -def write_pem_private_key(priv_key, filename): +def write_pem_private_key(priv_key, filename, passwd=None): """ Write a private key to a file in PEM format. Will force 0x600 permissions on file. :param priv_key: cryptography ``PrivateKey`` object + :param passwd: ``bytes`` representing the password to store the + private key with """ + if passwd is not None: + enc_alg = serialization.BestAvailableEncryption(passwd) + else: + enc_alg = serialization.NoEncryption() try: with open(filename, 'wb') as fp: os.fchmod(fp.fileno(), 0o600) fp.write(priv_key.private_bytes( Encoding.PEM, PrivateFormat.TraditionalOpenSSL, - serialization.NoEncryption())) + encryption_algorithm=enc_alg)) except (IOError, OSError) as e: raise errors.FileError(reason=str(e)) diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py index 69bf9a2f3..0e37c7151 100644 --- a/ipaplatform/base/paths.py +++ b/ipaplatform/base/paths.py @@ -52,7 +52,8 @@ class BasePathNamespace(object): HTTPD_NSS_CONF = "/etc/httpd/conf.d/nss.conf" HTTPD_SSL_CONF = "/etc/httpd/conf.d/ssl.conf" HTTPD_CERT_FILE = "/var/lib/ipa/certs/httpd.crt" - HTTPD_KEY_FILE = "/var/lib/ipa/certs/httpd.key" + HTTPD_KEY_FILE = "/var/lib/ipa/private/httpd.key" + IPA_PASSWD_DIR = "/var/lib/ipa/passwds" # only used on Fedora HTTPD_IPA_WSGI_MODULES_CONF = None OLD_IPA_KEYTAB = "/etc/httpd/conf/ipa.keytab" @@ -211,6 +212,7 @@ class BasePathNamespace(object): IPA_DNSKEYSYNCD = "/usr/libexec/ipa/ipa-dnskeysyncd" IPA_HTTPD_KDCPROXY = "/usr/libexec/ipa/ipa-httpd-kdcproxy" IPA_ODS_EXPORTER = "/usr/libexec/ipa/ipa-ods-exporter" + IPA_HTTPD_PASSWD_READER = "/usr/libexec/ipa/ipa-httpd-pwdreader" DNSSEC_KEYFROMLABEL = "/usr/sbin/dnssec-keyfromlabel-pkcs11" GETSEBOOL = "/usr/sbin/getsebool" GROUPADD = "/usr/sbin/groupadd" diff --git a/ipaserver/install/httpinstance.py b/ipaserver/install/httpinstance.py index e33d943f8..91de4071c 100644 --- a/ipaserver/install/httpinstance.py +++ b/ipaserver/install/httpinstance.py @@ -43,7 +43,7 @@ from ipapython.dn import DN import ipapython.errors from ipaserver.install import sysupgrade from ipalib import api, x509 -from ipalib.constants import IPAAPI_USER +from ipalib.constants import IPAAPI_USER, HTTPD_PASSWD_FILE_FMT from ipaplatform.constants import constants from ipaplatform.tasks import tasks from ipaplatform.paths import paths @@ -306,6 +306,15 @@ class HTTPInstance(service.Service): certmonger.stop() def __setup_ssl(self): + key_passwd_file = os.path.join( + paths.IPA_PASSWD_DIR, + HTTPD_PASSWD_FILE_FMT.format(host=api.env.host) + ) + with open(key_passwd_file, 'wb') as f: + os.fchmod(f.fileno(), 0o600) + pkey_passwd = ipautil.ipa_generate_password().encode('utf-8') + f.write(pkey_passwd) + if self.pkcs12_info: p12_certs, p12_priv_keys = certs.pkcs12_to_certkeys( *self.pkcs12_info) @@ -332,7 +341,8 @@ class HTTPInstance(service.Service): x509.write_certificate(self.cert, paths.HTTPD_CERT_FILE) x509.write_pem_private_key( server_certs_keys[0][1], - paths.HTTPD_KEY_FILE + paths.HTTPD_KEY_FILE, + passwd=pkey_passwd ) if self.ca_is_configured: @@ -364,6 +374,7 @@ class HTTPInstance(service.Service): dns=[self.fqdn], post_command='restart_httpd', storage='FILE', + passwd_fname=key_passwd_file ) finally: if prev_helper is not None: @@ -377,7 +388,7 @@ class HTTPInstance(service.Service): with open(paths.HTTPD_KEY_FILE, 'rb') as f: priv_key = x509.load_pem_private_key( - f.read(), None, backend=x509.default_backend()) + f.read(), pkey_passwd, backend=x509.default_backend()) # Verify we have a valid server cert if (priv_key.public_key().public_numbers() @@ -396,6 +407,11 @@ class HTTPInstance(service.Service): installutils.set_directive(paths.HTTPD_SSL_CONF, 'SSLCertificateKeyFile', paths.HTTPD_KEY_FILE, False) + installutils.set_directive( + paths.HTTPD_SSL_CONF, + 'SSLPassPhraseDialog', + 'exec:{passread}'.format(passread=paths.IPA_HTTPD_PASSWD_READER), + False) installutils.set_directive(paths.HTTPD_SSL_CONF, 'SSLCACertificateFile', paths.IPA_CA_CRT, False) @@ -500,6 +516,8 @@ class HTTPInstance(service.Service): paths.HTTP_CCACHE, paths.HTTPD_CERT_FILE, paths.HTTPD_KEY_FILE, + os.path.join(paths.IPA_PASSWD_DIR, + HTTPD_PASSWD_FILE_FMT.format(host=api.env.host)), paths.HTTPD_IPA_REWRITE_CONF, paths.HTTPD_IPA_CONF, paths.HTTPD_IPA_PKI_PROXY_CONF, diff --git a/ipaserver/install/ipa_server_certinstall.py b/ipaserver/install/ipa_server_certinstall.py index 669d80081..ec4b8d266 100644 --- a/ipaserver/install/ipa_server_certinstall.py +++ b/ipaserver/install/ipa_server_certinstall.py @@ -25,6 +25,7 @@ import tempfile import optparse # pylint: disable=deprecated-module from ipalib import x509 +from ipalib.constants import HTTPD_PASSWD_FILE_FMT from ipalib.install import certmonger from ipaplatform.paths import paths from ipapython import admintool @@ -155,8 +156,18 @@ class ServerCertInstall(admintool.AdminTool): ca_chain_fname=paths.IPA_CA_CRT, host_name=api.env.host ) + + key_passwd_path = os.path.join( + paths.IPA_PASSWD_DIR, + HTTPD_PASSWD_FILE_FMT.format(host=api.env.host) + ) + req_id = self.replace_key_cert_files( - cert, key, paths.HTTPD_CERT_FILE, paths.HTTPD_KEY_FILE, ca_cert, + cert, key, + cert_fname=paths.HTTPD_CERT_FILE, + key_fname=paths.HTTPD_KEY_FILE, + ca_cert=ca_cert, + passwd_fname=key_passwd_path, cmgr_post_command='restart_httpd') if req_id is not None: @@ -206,7 +217,7 @@ class ServerCertInstall(admintool.AdminTool): return cert, key, ca_cert def replace_key_cert_files( - self, cert, key, cert_fname, key_fname, ca_cert, + self, cert, key, cert_fname, key_fname, ca_cert, passwd_fname=None, profile=None, cmgr_post_command=None ): try: @@ -214,8 +225,13 @@ class ServerCertInstall(admintool.AdminTool): if ca_enabled: certmonger.stop_tracking(certfile=cert_fname) + pkey_passwd = None + if passwd_fname is not None: + with open(passwd_fname, 'rb') as f: + pkey_passwd = f.read() + x509.write_certificate(cert, cert_fname) - x509.write_pem_private_key(key, key_fname) + x509.write_pem_private_key(key, key_fname, pkey_passwd) if ca_enabled: # Start tracking only if the cert was issued by IPA CA @@ -227,6 +243,7 @@ class ServerCertInstall(admintool.AdminTool): if ca_cert == ipa_ca_cert: req_id = certmonger.start_tracking( (cert_fname, key_fname), + pinfile=passwd_fname, storage='FILE', post_command=cmgr_post_command )