Encrypt httpd key stored on disk

This commit adds configuration for HTTPD to encrypt/decrypt its
key which we currently store in clear on the disc.

A password-reading script is added for mod_ssl. This script is
extensible for the future use of directory server with the
expectation that key encryption/decription will be handled
similarly by its configuration.

https://pagure.io/freeipa/issue/7421

Reviewed-By: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Christian Heimes <cheimes@redhat.com>
This commit is contained in:
Stanislav Laznicka
2018-02-26 10:15:05 +01:00
committed by Christian Heimes
parent e7e06f6d78
commit 7cbd9bd429
9 changed files with 97 additions and 9 deletions

View File

@@ -1307,6 +1307,7 @@ fi
%{_libexecdir}/ipa/ipa-custodia %{_libexecdir}/ipa/ipa-custodia
%{_libexecdir}/ipa/ipa-custodia-check %{_libexecdir}/ipa/ipa-custodia-check
%{_libexecdir}/ipa/ipa-httpd-kdcproxy %{_libexecdir}/ipa/ipa-httpd-kdcproxy
%{_libexecdir}/ipa/ipa-httpd-pwdreader
%{_libexecdir}/ipa/ipa-pki-retrieve-key %{_libexecdir}/ipa/ipa-pki-retrieve-key
%{_libexecdir}/ipa/ipa-otpd %{_libexecdir}/ipa/ipa-otpd
%dir %{_libexecdir}/ipa/oddjob %dir %{_libexecdir}/ipa/oddjob
@@ -1458,6 +1459,8 @@ fi
%attr(700,root,root) %dir %{_localstatedir}/lib/ipa/sysupgrade %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/pki-ca
%attr(755,root,root) %dir %{_localstatedir}/lib/ipa/certs %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}/lib/ipa/pki-ca/publish
%ghost %{_localstatedir}/named/dyndb-ldap/ipa %ghost %{_localstatedir}/named/dyndb-ldap/ipa
%dir %attr(0700,root,root) %{_sysconfdir}/ipa/custodia %dir %attr(0700,root,root) %{_sysconfdir}/ipa/custodia

View File

@@ -25,11 +25,15 @@ install-exec-local:
$(INSTALL) -d -m 700 $(DESTDIR)$(localstatedir)/lib/ipa/sysupgrade $(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/pki-ca
$(INSTALL) -d -m 755 $(DESTDIR)$(localstatedir)/lib/ipa/certs $(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: uninstall-local:
-rmdir $(DESTDIR)$(localstatedir)/lib/ipa/sysrestore -rmdir $(DESTDIR)$(localstatedir)/lib/ipa/sysrestore
-rmdir $(DESTDIR)$(localstatedir)/lib/ipa/sysupgrade -rmdir $(DESTDIR)$(localstatedir)/lib/ipa/sysupgrade
-rmdir $(DESTDIR)$(localstatedir)/lib/ipa/certs -rmdir $(DESTDIR)$(localstatedir)/lib/ipa/certs
-rmdir $(DESTDIR)$(localstatedir)/lib/ipa/private
-rmdir $(DESTDIR)$(localstatedir)/lib/ipa/passwds
-rmdir $(DESTDIR)$(localstatedir)/lib/ipa -rmdir $(DESTDIR)$(localstatedir)/lib/ipa
EXTRA_DIST = README.schema EXTRA_DIST = README.schema

View File

@@ -36,5 +36,6 @@ dist_app_SCRIPTS = \
ipa-custodia \ ipa-custodia \
ipa-custodia-check \ ipa-custodia-check \
ipa-httpd-kdcproxy \ ipa-httpd-kdcproxy \
ipa-httpd-pwdreader \
ipa-pki-retrieve-key \ ipa-pki-retrieve-key \
$(NULL) $(NULL)

View File

@@ -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
# <hostname>-<port>-<ecryption_algorithm>
IPA_PASSWD_PATH="/var/lib/ipa/passwds/${1/:/-}-$2"
cat $IPA_PASSWD_PATH
;;
# ================
# Extend for more virtual hosts with
# <vhostname>:<vhost_port> )
# your_code
# ;;
# ================
*)
echo "$ERR_UNKNOWN_KEY" 1>&2
exit 1
esac

View File

@@ -319,3 +319,4 @@ USER_CACHE_PATH = (
) )
SOFTHSM_DNSSEC_TOKEN_LABEL = u'ipaDNSSEC' SOFTHSM_DNSSEC_TOKEN_LABEL = u'ipaDNSSEC'
HTTPD_PASSWD_FILE_FMT = "{host}-443-RSA"

View File

@@ -569,20 +569,26 @@ def write_certificate_list(certs, filename):
raise errors.FileError(reason=str(e)) 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 Write a private key to a file in PEM format. Will force 0x600 permissions
on file. on file.
:param priv_key: cryptography ``PrivateKey`` object :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: try:
with open(filename, 'wb') as fp: with open(filename, 'wb') as fp:
os.fchmod(fp.fileno(), 0o600) os.fchmod(fp.fileno(), 0o600)
fp.write(priv_key.private_bytes( fp.write(priv_key.private_bytes(
Encoding.PEM, Encoding.PEM,
PrivateFormat.TraditionalOpenSSL, PrivateFormat.TraditionalOpenSSL,
serialization.NoEncryption())) encryption_algorithm=enc_alg))
except (IOError, OSError) as e: except (IOError, OSError) as e:
raise errors.FileError(reason=str(e)) raise errors.FileError(reason=str(e))

View File

@@ -52,7 +52,8 @@ class BasePathNamespace(object):
HTTPD_NSS_CONF = "/etc/httpd/conf.d/nss.conf" HTTPD_NSS_CONF = "/etc/httpd/conf.d/nss.conf"
HTTPD_SSL_CONF = "/etc/httpd/conf.d/ssl.conf" HTTPD_SSL_CONF = "/etc/httpd/conf.d/ssl.conf"
HTTPD_CERT_FILE = "/var/lib/ipa/certs/httpd.crt" 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 # only used on Fedora
HTTPD_IPA_WSGI_MODULES_CONF = None HTTPD_IPA_WSGI_MODULES_CONF = None
OLD_IPA_KEYTAB = "/etc/httpd/conf/ipa.keytab" OLD_IPA_KEYTAB = "/etc/httpd/conf/ipa.keytab"
@@ -211,6 +212,7 @@ class BasePathNamespace(object):
IPA_DNSKEYSYNCD = "/usr/libexec/ipa/ipa-dnskeysyncd" IPA_DNSKEYSYNCD = "/usr/libexec/ipa/ipa-dnskeysyncd"
IPA_HTTPD_KDCPROXY = "/usr/libexec/ipa/ipa-httpd-kdcproxy" IPA_HTTPD_KDCPROXY = "/usr/libexec/ipa/ipa-httpd-kdcproxy"
IPA_ODS_EXPORTER = "/usr/libexec/ipa/ipa-ods-exporter" 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" DNSSEC_KEYFROMLABEL = "/usr/sbin/dnssec-keyfromlabel-pkcs11"
GETSEBOOL = "/usr/sbin/getsebool" GETSEBOOL = "/usr/sbin/getsebool"
GROUPADD = "/usr/sbin/groupadd" GROUPADD = "/usr/sbin/groupadd"

View File

@@ -43,7 +43,7 @@ from ipapython.dn import DN
import ipapython.errors import ipapython.errors
from ipaserver.install import sysupgrade from ipaserver.install import sysupgrade
from ipalib import api, x509 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.constants import constants
from ipaplatform.tasks import tasks from ipaplatform.tasks import tasks
from ipaplatform.paths import paths from ipaplatform.paths import paths
@@ -306,6 +306,15 @@ class HTTPInstance(service.Service):
certmonger.stop() certmonger.stop()
def __setup_ssl(self): 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: if self.pkcs12_info:
p12_certs, p12_priv_keys = certs.pkcs12_to_certkeys( p12_certs, p12_priv_keys = certs.pkcs12_to_certkeys(
*self.pkcs12_info) *self.pkcs12_info)
@@ -332,7 +341,8 @@ class HTTPInstance(service.Service):
x509.write_certificate(self.cert, paths.HTTPD_CERT_FILE) x509.write_certificate(self.cert, paths.HTTPD_CERT_FILE)
x509.write_pem_private_key( x509.write_pem_private_key(
server_certs_keys[0][1], server_certs_keys[0][1],
paths.HTTPD_KEY_FILE paths.HTTPD_KEY_FILE,
passwd=pkey_passwd
) )
if self.ca_is_configured: if self.ca_is_configured:
@@ -364,6 +374,7 @@ class HTTPInstance(service.Service):
dns=[self.fqdn], dns=[self.fqdn],
post_command='restart_httpd', post_command='restart_httpd',
storage='FILE', storage='FILE',
passwd_fname=key_passwd_file
) )
finally: finally:
if prev_helper is not None: if prev_helper is not None:
@@ -377,7 +388,7 @@ class HTTPInstance(service.Service):
with open(paths.HTTPD_KEY_FILE, 'rb') as f: with open(paths.HTTPD_KEY_FILE, 'rb') as f:
priv_key = x509.load_pem_private_key( 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 # Verify we have a valid server cert
if (priv_key.public_key().public_numbers() if (priv_key.public_key().public_numbers()
@@ -396,6 +407,11 @@ class HTTPInstance(service.Service):
installutils.set_directive(paths.HTTPD_SSL_CONF, installutils.set_directive(paths.HTTPD_SSL_CONF,
'SSLCertificateKeyFile', 'SSLCertificateKeyFile',
paths.HTTPD_KEY_FILE, False) 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, installutils.set_directive(paths.HTTPD_SSL_CONF,
'SSLCACertificateFile', 'SSLCACertificateFile',
paths.IPA_CA_CRT, False) paths.IPA_CA_CRT, False)
@@ -500,6 +516,8 @@ class HTTPInstance(service.Service):
paths.HTTP_CCACHE, paths.HTTP_CCACHE,
paths.HTTPD_CERT_FILE, paths.HTTPD_CERT_FILE,
paths.HTTPD_KEY_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_REWRITE_CONF,
paths.HTTPD_IPA_CONF, paths.HTTPD_IPA_CONF,
paths.HTTPD_IPA_PKI_PROXY_CONF, paths.HTTPD_IPA_PKI_PROXY_CONF,

View File

@@ -25,6 +25,7 @@ import tempfile
import optparse # pylint: disable=deprecated-module import optparse # pylint: disable=deprecated-module
from ipalib import x509 from ipalib import x509
from ipalib.constants import HTTPD_PASSWD_FILE_FMT
from ipalib.install import certmonger from ipalib.install import certmonger
from ipaplatform.paths import paths from ipaplatform.paths import paths
from ipapython import admintool from ipapython import admintool
@@ -155,8 +156,18 @@ class ServerCertInstall(admintool.AdminTool):
ca_chain_fname=paths.IPA_CA_CRT, ca_chain_fname=paths.IPA_CA_CRT,
host_name=api.env.host 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( 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') cmgr_post_command='restart_httpd')
if req_id is not None: if req_id is not None:
@@ -206,7 +217,7 @@ class ServerCertInstall(admintool.AdminTool):
return cert, key, ca_cert return cert, key, ca_cert
def replace_key_cert_files( 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 profile=None, cmgr_post_command=None
): ):
try: try:
@@ -214,8 +225,13 @@ class ServerCertInstall(admintool.AdminTool):
if ca_enabled: if ca_enabled:
certmonger.stop_tracking(certfile=cert_fname) 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_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: if ca_enabled:
# Start tracking only if the cert was issued by IPA CA # 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: if ca_cert == ipa_ca_cert:
req_id = certmonger.start_tracking( req_id = certmonger.start_tracking(
(cert_fname, key_fname), (cert_fname, key_fname),
pinfile=passwd_fname,
storage='FILE', storage='FILE',
post_command=cmgr_post_command post_command=cmgr_post_command
) )