diff options
27 files changed, 724 insertions, 18 deletions
diff --git a/freeipa.spec.in b/freeipa.spec.in index 719932d4b..6b24a0b0d 100644 --- a/freeipa.spec.in +++ b/freeipa.spec.in @@ -249,7 +249,7 @@ Requires: xmlrpc-c %endif %endif Requires: sssd >= 1.8.0 -Requires: certmonger >= 0.53 +Requires: certmonger >= 0.58 Requires: nss-tools Requires: bind-utils Requires: oddjob-mkhomedir @@ -571,6 +571,7 @@ fi %{_sbindir}/ipactl %{_sbindir}/ipa-upgradeconfig %{_sbindir}/ipa-compliance +%{_libexecdir}/certmonger/dogtag-ipa-retrieve-agent-submit %{_sysconfdir}/cron.d/ipa-compliance %config(noreplace) %{_sysconfdir}/sysconfig/ipa_memcached %dir %attr(0700,apache,apache) %{_localstatedir}/run/ipa_memcached/ @@ -633,6 +634,7 @@ fi %ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/httpd/conf.d/ipa-rewrite.conf %ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/httpd/conf.d/ipa.conf %ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/httpd/conf.d/ipa-pki-proxy.conf +%{_usr}/share/ipa/ca_renewal %{_usr}/share/ipa/ipa.conf %{_usr}/share/ipa/ipa-rewrite.conf %{_usr}/share/ipa/ipa-pki-proxy.conf @@ -745,6 +747,9 @@ fi %ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/ipa/ca.crt %changelog +* Tue Jul 24 2012 Rob Crittenden <rcritten@redhat.com> - 2.99.0-39 +- Set minimum certmonger to 0.58 for dogtag cert renewal + * Wed Jul 18 2012 Alexander Bokovoy <abokovoy@redhat.com> - 2.99.0-38 - Require samba4-devel >= 4.0.0-128 due to passdb API change in beta4 diff --git a/install/Makefile.am b/install/Makefile.am index 5670f9bd9..54c456a97 100644 --- a/install/Makefile.am +++ b/install/Makefile.am @@ -5,6 +5,7 @@ AUTOMAKE_OPTIONS = 1.7 NULL = SUBDIRS = \ + certmonger \ conf \ html \ migration \ diff --git a/install/certmonger/Makefile.am b/install/certmonger/Makefile.am new file mode 100644 index 000000000..2023a2aec --- /dev/null +++ b/install/certmonger/Makefile.am @@ -0,0 +1,14 @@ +NULL = + +appdir = $(libexecdir)/certmonger/ +app_SCRIPTS = \ + dogtag-ipa-retrieve-agent-submit \ + $(NULL) + +EXTRA_DIST = \ + $(app_SCRIPTS) \ + $(NULL) + +MAINTAINERCLEANFILES = \ + *~ \ + Makefile.in diff --git a/install/certmonger/dogtag-ipa-retrieve-agent-submit b/install/certmonger/dogtag-ipa-retrieve-agent-submit new file mode 100644 index 000000000..24e1844a5 --- /dev/null +++ b/install/certmonger/dogtag-ipa-retrieve-agent-submit @@ -0,0 +1,80 @@ +#!/usr/bin/python -E +# +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# +# Copyright (C) 2012 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# The certificate rewewal is done on only one dogtag CA. The others +# retrieve the updated certificate from IPA. + +import os +import sys +import shutil +import tempfile +import krbV +import syslog +from ipalib import api +from ipalib.dn import DN +from ipalib import errors +from ipalib import x509 +from ipapython import services as ipaservices +from ipapython import ipautil +from ipaserver.install import certs +from ipaserver.plugins.ldap2 import ldap2 +import base64 + +# We cheat and pass in the nickname as the CA profile to execute against. +# Some way is needed to determine which entry to retrieve from LDAP +operation = os.environ.get('CERTMONGER_OPERATION') +nickname = os.environ.get('CERTMONGER_CA_PROFILE') + +if operation not in ['SUBMIT', 'POLL']: + sys.exit(6) # unsupported operation + +api.bootstrap(context='renew') +api.finalize() + +# Update or add it +tmpdir = tempfile.mkdtemp(prefix = "tmp-") +try: + dn = str(DN(('cn',nickname),('cn=ca_renewal,cn=ipa,cn=etc'),(api.env.basedn))) + principal = str('host/%s@%s' % (api.env.host, api.env.realm)) + ccache = ipautil.kinit_hostprincipal('/etc/krb5.keytab', tmpdir, principal) + conn = ldap2(shared_instance=False, ldap_uri=api.env.ldap_uri) + conn.connect(ccache=ccache) + try: + syslog.syslog(syslog.LOG_NOTICE, "Updating certificate for %s" % nickname) + (entry_dn, entry_attrs) = conn.get_entry(dn, ['usercertificate']) + cert = entry_attrs['usercertificate'][0] + cert = base64.b64encode(cert) + print x509.make_pem(cert) + except errors.NotFound: + syslog.syslog(syslog.LOG_INFO, "Updated certificate for %s not available" % nickname) + # No cert available yet, tell certmonger to wait another 8 hours + print 8 * 60 * 60 + sys.exit(5) + finally: + conn.disconnect() +except Exception, e: + syslog.syslog(syslog.LOG_ERR, "Exception trying to retrieve %s: %s" % (nickname, e)) + # Unhandled error + sys.exit(3) +finally: + shutil.rmtree(tmpdir) + +sys.exit(0) diff --git a/install/conf/Makefile.am b/install/conf/Makefile.am index 5ee3eddb5..06b3b32df 100644 --- a/install/conf/Makefile.am +++ b/install/conf/Makefile.am @@ -2,8 +2,9 @@ NULL = appdir = $(IPA_DATA_DIR) app_DATA = \ + ca_renewal \ ipa.conf \ - ipa-pki-proxy.conf \ + ipa-pki-proxy.conf \ ipa-rewrite.conf \ $(NULL) diff --git a/install/conf/ca_renewal b/install/conf/ca_renewal new file mode 100644 index 000000000..57a9e9c24 --- /dev/null +++ b/install/conf/ca_renewal @@ -0,0 +1,6 @@ +# A separate helper for fetching dogtag certificates that are renewed on +# another system. +id=dogtag-ipa-retrieve-agent-submit +ca_is_default=0 +ca_type=EXTERNAL +ca_external_helper=/usr/libexec/certmonger/dogtag-ipa-retrieve-agent-submit diff --git a/install/configure.ac b/install/configure.ac index 9e781a684..c5934d93d 100644 --- a/install/configure.ac +++ b/install/configure.ac @@ -75,6 +75,7 @@ AC_SUBST(IPA_SYSCONF_DIR) AC_CONFIG_FILES([ Makefile + certmonger/Makefile conf/Makefile html/Makefile migration/Makefile diff --git a/install/restart_scripts/Makefile.am b/install/restart_scripts/Makefile.am index abc066b30..210c4863e 100644 --- a/install/restart_scripts/Makefile.am +++ b/install/restart_scripts/Makefile.am @@ -4,6 +4,9 @@ appdir = $(libdir)/ipa/certmonger app_DATA = \ restart_dirsrv \ restart_httpd \ + restart_pkicad \ + renew_ca_cert \ + renew_ra_cert \ $(NULL) EXTRA_DIST = \ diff --git a/install/restart_scripts/renew_ca_cert b/install/restart_scripts/renew_ca_cert new file mode 100644 index 000000000..d3b756042 --- /dev/null +++ b/install/restart_scripts/renew_ca_cert @@ -0,0 +1,93 @@ +#!/usr/bin/python -E +# +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# +# Copyright (C) 2012 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import sys +import shutil +import tempfile +import krbV +import syslog +from ipalib import api +from ipalib.dn import DN +from ipalib import errors +from ipapython import services as ipaservices +from ipapython import ipautil +from ipaserver.install import certs +from ipaserver.plugins.ldap2 import ldap2 +from ipaserver.install.cainstance import update_cert_config + +nickname = sys.argv[1] + +api.bootstrap(context='restart') +api.finalize() + +# Fetch the new certificate +db = certs.CertDB(api.env.realm, nssdir='/var/lib/pki-ca/alias') +cert = db.get_cert_from_db(nickname, pem=False) + +if not cert: + syslog.syslog(syslog.LOG_ERR, 'No certificate %s found.' % nickname) + sys.exit(1) + +# Update or add it +tmpdir = tempfile.mkdtemp(prefix = "tmp-") +try: + dn = str(DN(('cn',nickname),('cn=ca_renewal,cn=ipa,cn=etc'),(api.env.basedn))) + principal = str('host/%s@%s' % (api.env.host, api.env.realm)) + ccache = ipautil.kinit_hostprincipal('/etc/krb5.keytab', tmpdir, principal) + conn = ldap2(shared_instance=False, ldap_uri=api.env.ldap_uri) + conn.connect(ccache=ccache) + try: + (entry_dn, entry_attrs) = conn.get_entry(dn, ['usercertificate']) + entry_attrs['usercertificate'] = cert + conn.update_entry(dn, entry_attrs, normalize=False) + except errors.NotFound: + entry_attrs = dict(objectclass=['top', 'pkiuser', 'nscontainer'], + usercertificate=cert) + conn.add_entry(dn, entry_attrs, normalize=False) + except errors.EmptyModlist: + pass + conn.disconnect() +except Exception, e: + syslog.syslog(syslog.LOG_ERR, 'Updating renewal certificate failed: %s' % e) +finally: + shutil.rmtree(tmpdir) + +# Fix permissions on the audit cert if we're updating it +if nickname == 'auditSigningCert cert-pki-ca': + db = certs.CertDB(api.env.realm, nssdir='/var/lib/pki-ca/alias') + args = ['-M', + '-n', nickname, + '-t', 'u,u,Pu', + ] + try: + db.run_certutil(args) + except ipautil.CalledProcessError: + syslog.syslog(syslog.LOG_ERR, 'Updating trust on certificate %s failed in %s' % (nickname, db.secdir)) + +update_cert_config(nickname, cert) + +syslog.syslog(syslog.LOG_NOTICE, 'certmonger restarted pki-cad instance pki-ca') + +try: + ipaservices.knownservices.pki_cad.restart('pki-ca') +except Exception, e: + syslog.syslog(syslog.LOG_ERR, "Cannot restart pki-cad: %s" % str(e)) diff --git a/install/restart_scripts/renew_ra_cert b/install/restart_scripts/renew_ra_cert new file mode 100644 index 000000000..2fcf1a79b --- /dev/null +++ b/install/restart_scripts/renew_ra_cert @@ -0,0 +1,96 @@ +#!/usr/bin/python -E +# +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# +# Copyright (C) 2012 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys +import shutil +import tempfile +import syslog +from ipapython import services as ipaservices +from ipapython.certmonger import get_pin +from ipapython import ipautil +from ipaserver.install import certs +from ipaserver.install.cainstance import DEFAULT_DSPORT +from ipalib import api +from ipalib.dn import DN +from ipalib import x509 +from ipalib import errors +from ipaserver.plugins.ldap2 import ldap2 + +api.bootstrap(context='restart') +api.finalize() + +# Fetch the new certificate +db = certs.CertDB(api.env.realm) +cert = db.get_cert_from_db('ipaCert', pem=False) +serial_number = x509.get_serial_number(cert, datatype=x509.DER) +subject = x509.get_subject(cert, datatype=x509.DER) +issuer = x509.get_issuer(cert, datatype=x509.DER) + +# Load it into dogtag +dn = str(DN(('uid','ipara'),('ou','People'),('o','ipaca'))) + +try: + dm_password = get_pin('internaldb') +except IOError, e: + syslog.syslog(syslog.LOG_ERR, 'Unable to determine PIN for CA instance: %s' % e) + sys.exit(1) + +try: + conn = ldap2(shared_instance=False, ldap_uri='ldap://localhost:%d' % DEFAULT_DSPORT) + conn.connect(bind_dn='cn=directory manager', bind_pw=dm_password) + (entry_dn, entry_attrs) = conn.get_entry(dn, ['usercertificate'], normalize=False) + entry_attrs['usercertificate'].append(cert) + entry_attrs['description'] = '2;%d;%s;%s' % (serial_number, issuer, subject) + conn.update_entry(dn, entry_attrs, normalize=False) + conn.disconnect() +except Exception, e: + syslog.syslog(syslog.LOG_ERR, 'Updating agent entry failed: %s' % e) + sys.exit(1) + +# Store it in the IPA LDAP server +tmpdir = tempfile.mkdtemp(prefix = "tmp-") +try: + dn = str(DN(('cn','ipaCert'),('cn=ca_renewal,cn=ipa,cn=etc'),(api.env.basedn))) + principal = str('host/%s@%s' % (api.env.host, api.env.realm)) + ccache = ipautil.kinit_hostprincipal('/etc/krb5.keytab', tmpdir, principal) + conn = ldap2(shared_instance=False, ldap_uri=api.env.ldap_uri) + conn.connect(ccache=ccache) + try: + (entry_dn, entry_attrs) = conn.get_entry(dn, ['usercertificate']) + entry_attrs['usercertificate'] = cert + conn.update_entry(dn, entry_attrs, normalize=False) + except errors.NotFound: + entry_attrs = dict(objectclass=['top', 'pkiuser', 'nscontainer'], + usercertificate=cert) + conn.add_entry(dn, entry_attrs, normalize=False) + except errors.EmptyModlist: + pass + conn.disconnect() +except Exception, e: + syslog.syslog(syslog.LOG_ERR, 'Updating renewal certificate failed: %s' % e) +finally: + shutil.rmtree(tmpdir) + +# Now restart Apache so the new certificate is available +try: + ipaservices.knownservices.httpd.restart() +except Exception, e: + syslog.syslog(syslog.LOG_ERR, "Cannot restart httpd: %s" % str(e)) diff --git a/install/restart_scripts/restart_dirsrv b/install/restart_scripts/restart_dirsrv index e243583f9..d6bbbbc3f 100644 --- a/install/restart_scripts/restart_dirsrv +++ b/install/restart_scripts/restart_dirsrv @@ -1,5 +1,26 @@ #!/usr/bin/python -E +# +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# +# Copyright (C) 2012 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + import sys +import syslog from ipapython import services as ipaservices try: @@ -7,7 +28,9 @@ try: except IndexError: instance = "" +syslog.syslog(syslog.LOG_NOTICE, "certmonger restarted dirsrv instance '%s'" % instance) + try: ipaservices.knownservices.dirsrv.restart(instance) except Exception, e: - print "Cannot restart dirsrv (instance: '%s'): %s" % (instance, str(e)) + syslog.syslog(syslog.LOG_ERR, "Cannot restart dirsrv (instance: '%s'): %s" % (instance, str(e))) diff --git a/install/restart_scripts/restart_httpd b/install/restart_scripts/restart_httpd index a53ab6e62..96f80bd8e 100644 --- a/install/restart_scripts/restart_httpd +++ b/install/restart_scripts/restart_httpd @@ -1,7 +1,30 @@ #!/usr/bin/python -E +# +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# +# Copyright (C) 2012 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import syslog from ipapython import services as ipaservices +syslog.syslog(syslog.LOG_NOTICE, 'certmonger restarted httpd') + try: ipaservices.knownservices.httpd.restart() except Exception, e: - print "Cannot restart httpd: %s" % str(e) + syslog.syslog(syslog.LOG_ERR, "Cannot restart httpd: %s" % str(e)) diff --git a/install/restart_scripts/restart_pkicad b/install/restart_scripts/restart_pkicad new file mode 100644 index 000000000..070760b16 --- /dev/null +++ b/install/restart_scripts/restart_pkicad @@ -0,0 +1,50 @@ +#!/usr/bin/python -E +# +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# +# Copyright (C) 2012 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys +import syslog +from ipapython import services as ipaservices +from ipaserver.install import certs +from ipalib import api + +nickname = sys.argv[1] + +api.bootstrap(context='restart') +api.finalize() + +syslog.syslog(syslog.LOG_NOTICE, "certmonger restarted pki-cad, nickname '%s'" % nickname) + +# Fix permissions on the audit cert if we're updating it +if nickname == 'auditSigningCert cert-pki-ca': + db = certs.CertDB(api.env.realm, nssdir='/var/lib/pki-ca/alias') + args = ['-M', + '-n', nickname, + '-t', 'u,u,Pu', + ] + db.run_certutil(args) + +try: + # I've seen times where systemd restart does not actually restart + # the process. A full stop/start is required. This works around that + ipaservices.knownservices.pki_cad.stop('pki-ca') + ipaservices.knownservices.pki_cad.start('pki-ca') +except Exception, e: + syslog.syslog(syslog.LOG_ERR, "Cannot restart pki-cad: %s" % str(e)) diff --git a/install/share/bootstrap-template.ldif b/install/share/bootstrap-template.ldif index 23510c953..aac3f059a 100644 --- a/install/share/bootstrap-template.ldif +++ b/install/share/bootstrap-template.ldif @@ -161,6 +161,12 @@ objectClass: nsContainer objectClass: top cn: posix-ids +dn: cn=ca_renewal,cn=ipa,cn=etc,$SUFFIX +changetype: add +objectClass: nsContainer +objectClass: top +cn: ca_renewal + dn: cn=s4u2proxy,cn=etc,$SUFFIX changetype: add objectClass: nsContainer diff --git a/install/share/default-aci.ldif b/install/share/default-aci.ldif index 870ac12e9..f3ed39599 100644 --- a/install/share/default-aci.ldif +++ b/install/share/default-aci.ldif @@ -86,3 +86,13 @@ changetype: modify add: aci aci: (targetattr="userPassword || krbPrincipalKey")(version 3.0; acl "Search existence of password and kerberos keys"; allow(search) userdn = "ldap:///all";) +# Let host add and update CA renewal certificates +dn: cn=ipa,cn=etc,$SUFFIX +changetype: modify +add: aci +aci: (target="ldap:///cn=*,cn=ca_renewal,cn=ipa,cn=etc,$SUFFIX")(version 3.0; acl "Add CA Certificates for renewals"; allow(add) userdn = "ldap:///fqdn=$FQDN,cn=computers,cn=accounts,$SUFFIX";) + +dn: cn=ipa,cn=etc,$SUFFIX +changetype: modify +add: aci +aci: (target="ldap:///cn=*,cn=ca_renewal,cn=ipa,cn=etc,$SUFFIX")(targetattr="userCertificate")(version 3.0; acl "Modify CA Certificates for renewals"; allow(write) userdn = "ldap:///fqdn=$FQDN,cn=computers,cn=accounts,$SUFFIX";) diff --git a/install/tools/ipa-replica-install b/install/tools/ipa-replica-install index 063eea023..c322cb62e 100755 --- a/install/tools/ipa-replica-install +++ b/install/tools/ipa-replica-install @@ -461,6 +461,7 @@ def main(): krb = install_krb(config, setup_pkinit=options.setup_pkinit) http = install_http(config, auto_redirect=options.ui_redirect) if CA: + CA.configure_certmonger_renewal() CA.import_ra_cert(dir + "/ra.p12") CA.fix_ra_perms() ipaservices.knownservices.httpd.restart() diff --git a/install/tools/ipa-upgradeconfig b/install/tools/ipa-upgradeconfig index cfb9a19e3..951bd4854 100644 --- a/install/tools/ipa-upgradeconfig +++ b/install/tools/ipa-upgradeconfig @@ -28,6 +28,7 @@ try: from ipapython import ipautil, sysrestore, version from ipapython.config import IPAOptionParser from ipapython.ipa_log_manager import * + from ipapython import certmonger from ipaserver.install import installutils from ipaserver.install import dsinstance from ipaserver.install import httpinstance @@ -43,6 +44,7 @@ try: import os import shutil import fileinput + from ipalib import api import ipalib.errors except ImportError: print >> sys.stderr, """\ @@ -430,6 +432,35 @@ def named_enable_serial_autoincrement(): return changed +def enable_certificate_renewal(realm): + """ + If the CA subsystem certificates are not being tracked for renewal then + tell certmonger to start tracking them. + """ + ca = cainstance.CAInstance(realm, certs.NSS_DIR) + if not ca.is_configured(): + root_logger.debug('dogtag not configured') + return + + # Using the nickname find the certmonger request_id + criteria = (('cert_storage_location', '/etc/httpd/alias', certmonger.NPATH),('cert_nickname', 'ipaCert', None)) + request_id = certmonger.get_request_id(criteria) + if request_id is not None: + root_logger.debug('Certificate renewal already configured') + return + + if not sysupgrade.get_upgrade_state('dogtag', 'renewal_configured'): + if ca.is_master(): + ca.configure_renewal() + else: + ca.configure_certmonger_renewal() + ca.configure_clone_renewal() + ca.configure_agent_renewal() + ca.track_servercert() + sysupgrade.set_upgrade_state('dogtag', 'renewal_configured', True) + ca.restart(cainstance.PKI_INSTANCE_NAME) + root_logger.debug('CA subsystem certificate renewal enabled') + def main(): """ Get some basics about the system. If getting those basics fail then @@ -440,6 +471,9 @@ def main(): if not os.geteuid()==0: sys.exit("\nYou must be root to run this script.\n") + if not installutils.is_ipa_configured(): + sys.exit(0) + safe_options, options = parse_options() standard_logging_setup('/var/log/ipaupgrade.log', verbose=True, @@ -448,11 +482,8 @@ def main(): fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') - try: - krbctx = krbV.default_context() - except krbV.Krb5Error, e: - # Unable to get default kerberos realm - sys.exit(0) + api.bootstrap(context='restart') + api.finalize() fqdn = find_hostname() if fqdn is None: @@ -464,13 +495,13 @@ def main(): check_certs() auto_redirect = find_autoredirect(fqdn) - sub_dict = { "REALM" : krbctx.default_realm, "FQDN": fqdn, "AUTOREDIR": '' if auto_redirect else '#'} + sub_dict = { "REALM" : api.env.realm, "FQDN": fqdn, "AUTOREDIR": '' if auto_redirect else '#'} upgrade(sub_dict, "/etc/httpd/conf.d/ipa.conf", ipautil.SHARE_DIR + "ipa.conf") upgrade(sub_dict, "/etc/httpd/conf.d/ipa-rewrite.conf", ipautil.SHARE_DIR + "ipa-rewrite.conf") upgrade(sub_dict, "/etc/httpd/conf.d/ipa-pki-proxy.conf", ipautil.SHARE_DIR + "ipa-pki-proxy.conf", add=True) upgrade_pki(fstore) - update_dbmodules(krbctx.default_realm) + update_dbmodules(api.env.realm) uninstall_ipa_kpasswd() http = httpinstance.HTTPInstance(fstore) @@ -479,25 +510,26 @@ def main(): memcache = memcacheinstance.MemcacheInstance() memcache.ldapi = True - memcache.realm = krbctx.default_realm + memcache.realm = api.env.realm try: if not memcache.is_configured(): # 389-ds needs to be running to create the memcache instance # because we record the new service in cn=masters. ds = dsinstance.DsInstance() ds.start() - memcache.create_instance('MEMCACHE', fqdn, None, ipautil.realm_to_suffix(krbctx.default_realm)) + memcache.create_instance('MEMCACHE', fqdn, None, ipautil.realm_to_suffix(api.env.realm)) except (ldap.ALREADY_EXISTS, ipalib.errors.DuplicateEntry): pass cleanup_kdc(fstore) - upgrade_ipa_profile(krbctx.default_realm) + upgrade_ipa_profile(api.env.realm) changed_psearch = named_enable_psearch() changed_autoincrement = named_enable_serial_autoincrement() if changed_psearch or changed_autoincrement: # configuration has changed, restart the name server root_logger.info('Changes to named.conf have been made, restart named') bindinstance.BindInstance(fstore).restart() + enable_certificate_renewal(api.env.realm) if __name__ == '__main__': installutils.run_script(main, operation_name='ipa-upgradeconfig') diff --git a/install/updates/21-ca_renewal_container.update b/install/updates/21-ca_renewal_container.update new file mode 100644 index 000000000..50b92d73d --- /dev/null +++ b/install/updates/21-ca_renewal_container.update @@ -0,0 +1,8 @@ +# +# Add CA renewal container if not available +# + +dn: cn=ca_renewal,cn=ipa,cn=etc,$SUFFIX +add:objectClass: top +add:objectClass: nsContainer +add:cn: ca_renewal diff --git a/install/updates/40-delegation.update b/install/updates/40-delegation.update index de112d99d..1e512d0f7 100644 --- a/install/updates/40-delegation.update +++ b/install/updates/40-delegation.update @@ -356,3 +356,7 @@ replace:aci:'(target = "ldap:///uid=*,cn=users,cn=accounts,$SUFFIX")(targetattr # Don't allow the default 'manage group membership' to be able to manage the # admins group replace:aci:'(targetattr = "member")(target = "ldap:///cn=*,cn=groups,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Modify Group membership";allow (write) groupdn = "ldap:///cn=Modify Group membership,cn=permissions,cn=pbac,$SUFFIX";)::(targetfilter = "(!(cn=admins))")(targetattr = "member")(target = "ldap:///cn=*,cn=groups,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Modify Group membership";allow (write) groupdn = "ldap:///cn=Modify Group membership,cn=permissions,cn=pbac,$SUFFIX";)' + +dn: cn=ipa,cn=etc,$SUFFIX +add:aci:'(target = "ldap:///cn=*,cn=ca_renewal,cn=ipa,cn=etc,$SUFFIX")(version 3.0; acl "Add CA Certificates for renewals"; allow(add) userdn = "ldap:///fqdn=$FQDN,cn=computers,cn=accounts,$SUFFIX";)' +add:aci:'(target = "ldap:///cn=*,cn=ca_renewal,cn=ipa,cn=etc,$SUFFIX")(targetattr = "userCertificate")(version 3.0; acl "Modify CA Certificates for renewals"; allow(write) userdn = "ldap:///fqdn=$FQDN,cn=computers,cn=accounts,$SUFFIX";)' diff --git a/install/updates/Makefile.am b/install/updates/Makefile.am index e45690f14..bc7945d7a 100644 --- a/install/updates/Makefile.am +++ b/install/updates/Makefile.am @@ -22,6 +22,7 @@ app_DATA = \ 20-user_private_groups.update \ 20-winsync_index.update \ 21-replicas_container.update \ + 21-ca_renewal_container.update \ 30-s4u2proxy.update \ 40-delegation.update \ 40-dns.update \ diff --git a/ipalib/x509.py b/ipalib/x509.py index 1274673c3..1b133adfb 100644 --- a/ipalib/x509.py +++ b/ipalib/x509.py @@ -141,6 +141,14 @@ def get_subject(certificate, datatype=PEM, dbdir=None): nsscert = load_certificate(certificate, datatype, dbdir) return nsscert.subject +def get_issuer(certificate, datatype=PEM, dbdir=None): + """ + Load an X509.3 certificate and get the issuer. + """ + + nsscert = load_certificate(certificate, datatype, dbdir) + return nsscert.issuer + def get_serial_number(certificate, datatype=PEM, dbdir=None): """ Return the decimal value of the serial number. diff --git a/ipapython/certmonger.py b/ipapython/certmonger.py index 22a599ae6..bdc8591e7 100644 --- a/ipapython/certmonger.py +++ b/ipapython/certmonger.py @@ -22,6 +22,7 @@ # server certificates created during the IPA server installation. import os +import sys import re import time from ipapython import ipautil @@ -329,6 +330,70 @@ def remove_principal_from_cas(): fp.write(line) fp.close() +# Routines specific to renewing dogtag CA certificates +def get_pin(token): + """ + Dogtag stores its NSS pin in a file formatted as token:PIN. + + The caller is expected to handle any exceptions raised. + """ + filename = '/var/lib/pki-ca/conf/password.conf' + with open(filename, 'r') as f: + for line in f: + (tok, pin) = line.split('=', 1) + if token == tok: + return pin.strip() + return None + +def dogtag_start_tracking(ca, nickname, pin, pinfile, secdir, command): + """ + Tell certmonger to start tracking a dogtag CA certificate. These + are handled differently because their renewal must be done directly + and not through IPA. + + This uses the generic certmonger command getcert so we can specify + a different helper. + + command is the script to execute. + + Returns the stdout, stderr and returncode from running ipa-getcert + + This assumes that certmonger is already running. + """ + if not cert_exists(nickname, os.path.abspath(secdir)): + raise RuntimeError('Nickname "%s" doesn\'t exist in NSS database "%s"' % (nickname, secdir)) + + if command is not None and not os.path.isabs(command): + if sys.maxsize > 2**32: + libpath = 'lib64' + else: + libpath = 'lib' + command = '/usr/%s/ipa/certmonger/%s' % (libpath, command) + + args = ["/usr/bin/getcert", "start-tracking", + "-d", os.path.abspath(secdir), + "-n", nickname, + "-c", ca, + "-C", command, + ] + + if pinfile: + args.append("-p") + args.append(pinfile) + else: + args.append("-P") + args.append(pin) + + if ca == 'dogtag-ipa-retrieve-agent-submit': + # We cheat and pass in the nickname as the profile when + # renewing on a clone. The submit otherwise doesn't pass in the + # nickname and we need some way to find the right entry in LDAP. + args.append("-T") + args.append(nickname) + + (stdout, stderr, returncode) = ipautil.run(args, nolog=[pin]) + + if __name__ == '__main__': request_id = request_cert("/etc/httpd/alias", "Test", "cn=tiger.example.com,O=IPA", "HTTP/tiger.example.com@EXAMPLE.COM") csr = get_request_value(request_id, 'csr') diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py index 22c8e2937..bed5435b5 100644 --- a/ipapython/ipautil.py +++ b/ipapython/ipautil.py @@ -42,6 +42,7 @@ import xmlrpclib import datetime import netaddr import time +import krbV from dns import resolver, rdatatype from dns.exception import DNSException @@ -1086,3 +1087,25 @@ def wait_for_open_socket(socket_name, timeout=0): time.sleep(1) else: raise e + +def kinit_hostprincipal(keytab, ccachedir, principal): + """ + Given a ccache directory and a principal kinit as that user. + + This blindly overwrites the current CCNAME so if you need to save + it do so before calling this function. + + Thus far this is used to kinit as the local host. + """ + try: + ccache_file = 'FILE:%s/ccache' % ccachedir + krbcontext = krbV.default_context() + ktab = krbV.Keytab(name=keytab, context=krbcontext) + princ = krbV.Principal(name=principal, context=krbcontext) + os.environ['KRB5CCNAME'] = ccache_file + ccache = krbV.CCache(name=ccache_file, context=krbcontext, primary_principal=princ) + ccache.init(princ) + ccache.init_creds_keytab(keytab=ktab, principal=princ) + return ccache_file + except krbV.Krb5Error, e: + raise StandardError('Error initializing principal %s in %s: %s' % (principal, keytab, str(e))) diff --git a/ipapython/platform/base.py b/ipapython/platform/base.py index 6f9d3867a..8c694ac04 100644 --- a/ipapython/platform/base.py +++ b/ipapython/platform/base.py @@ -25,7 +25,7 @@ from ipalib.plugable import MagicDict wellknownservices = ['certmonger', 'dirsrv', 'httpd', 'ipa', 'krb5kdc', 'messagebus', 'nslcd', 'nscd', 'ntpd', 'portmap', 'rpcbind', 'kadmin', 'sshd', 'autofs', 'rpcgssd', - 'rpcidmapd'] + 'rpcidmapd', 'pki_cad'] # The common ports for these services. This is used to wait for the diff --git a/ipapython/platform/fedora16.py b/ipapython/platform/fedora16.py index 8b730e41c..100bbb2ab 100644 --- a/ipapython/platform/fedora16.py +++ b/ipapython/platform/fedora16.py @@ -60,6 +60,7 @@ system_units['dirsrv'] = 'dirsrv@.service' system_units['pkids'] = 'dirsrv@PKI-IPA.service' # Our PKI instance is pki-cad@pki-ca.service system_units['pki-cad'] = 'pki-cad@pki-ca.service' +system_units['pki_cad'] = system_units['pki-cad'] class Fedora16Service(systemd.SystemdService): def __init__(self, service_name): diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py index 62c1dc4d0..2644689a0 100644 --- a/ipaserver/install/cainstance.py +++ b/ipaserver/install/cainstance.py @@ -37,6 +37,7 @@ import stat import socket from ipapython import dogtag from ipapython.certdb import get_ca_nickname +from ipapython import certmonger from ipalib import pkcs10, x509 from ipalib.dn import DN import subprocess @@ -324,7 +325,7 @@ class CADSInstance(service.Service): # We only handle one server cert self.nickname = server_certs[0][0] self.dercert = dsdb.get_cert_from_db(self.nickname, pem=False) - dsdb.track_server_cert(self.nickname, self.principal, dsdb.passwd_fname) + dsdb.track_server_cert(self.nickname, self.principal, dsdb.passwd_fname, 'restart_dirsrv %s' % self.serverid) def create_certdb(self): """ @@ -337,7 +338,7 @@ class CADSInstance(service.Service): cadb.export_ca_cert('ipaCert', False) dsdb.create_from_cacert(cadb.cacert_fname, passwd=None) self.dercert = dsdb.create_server_cert("Server-Cert", self.fqdn, cadb) - dsdb.track_server_cert("Server-Cert", self.principal, dsdb.passwd_fname) + dsdb.track_server_cert("Server-Cert", self.principal, dsdb.passwd_fname, 'restart_dirsrv %s' % self.serverid) dsdb.create_pin_file() def enable_ssl(self): @@ -404,6 +405,24 @@ class CADSInstance(service.Service): # At one time we removed this user on uninstall. That can potentially # orphan files, or worse, if another useradd runs in the intermim, # cause files to have a new owner. + cmonger = ipaservices.knownservices.certmonger + ipaservices.knownservices.messagebus.start() + cmonger.start() + + for nickname in ['Server-Cert cert-pki-ca', + 'auditSigningCert cert-pki-ca', + 'ocspSigningCert cert-pki-ca', + 'subsystemCert cert-pki-ca']: + try: + certmonger.stop_tracking('/var/lib/pki-ca/alias', nickname=nickname) + except (ipautil.CalledProcessError, RuntimeError), e: + root_logger.error("certmonger failed to stop tracking certificate: %s" % str(e)) + + try: + certmonger.stop_tracking('/etc/httpd/alias', nickname='ipaCert') + except (ipautil.CalledProcessError, RuntimeError), e: + root_logger.error("certmonger failed to stop tracking certificate: %s" % str(e)) + cmonger.stop() class CAInstance(service.Service): """ @@ -526,6 +545,11 @@ class CAInstance(service.Service): self.step("requesting RA certificate from CA", self.__request_ra_certificate) self.step("issuing RA agent certificate", self.__issue_ra_cert) self.step("adding RA agent as a trusted user", self.__configure_ra) + self.step("configure certificate renewals", self.configure_renewal) + else: + self.step("configure certmonger for renewals", self.configure_certmonger_renewal) + self.step("configure clone certificate renewals", self.configure_clone_renewal) + self.step("configure Server-Cert certificate renewal", self.track_servercert) self.step("Configure HTTP to proxy connections", self.__http_proxy) self.start_creation("Configuring certificate server", 210) @@ -797,6 +821,18 @@ class CAInstance(service.Service): finally: os.remove(agent_name) + self.configure_agent_renewal() + + def configure_agent_renewal(self): + """ + Set up the agent cert for renewal. No need to make any changes to + the dogtag LDAP here since the originator will do that so we + only call restart_httpd after retrieving the cert. + + On upgrades this needs to be called from ipa-upgradeconfig. + """ + certmonger.dogtag_start_tracking('dogtag-ipa-retrieve-agent-submit', 'ipaCert', None, '/etc/httpd/alias/pwdfile.txt', '/etc/httpd/alias', 'restart_httpd') + def __configure_ra(self): # Create an RA user in the CA LDAP server and add that user to # the appropriate groups so it can issue certificates without @@ -1058,6 +1094,8 @@ class CAInstance(service.Service): # cause files to have a new owner. user_exists = self.restore_state("user_exists") + installutils.remove_file("/var/lib/certmonger/cas/ca_renewal") + def publish_ca_cert(self, location): args = ["-L", "-n", self.canickname, "-a"] (cert, err, returncode) = self.__run_certutil(args) @@ -1070,6 +1108,77 @@ class CAInstance(service.Service): shutil.copy(ipautil.SHARE_DIR + "ipa-pki-proxy.conf", HTTPD_CONFD + "ipa-pki-proxy.conf") + def track_servercert(self): + try: + pin = certmonger.get_pin('internal') + except IOError, e: + raise RuntimeError('Unable to determine PIN for CA instance: %s' % str(e)) + certmonger.dogtag_start_tracking('dogtag-ipa-renew-agent', 'Server-Cert cert-pki-ca', pin, None, '/var/lib/pki-ca/alias', 'restart_pkicad "Server-Cert cert-pki-ca"') + + def configure_renewal(self): + cmonger = ipaservices.knownservices.certmonger + cmonger.enable() + ipaservices.knownservices.messagebus.start() + cmonger.start() + + try: + pin = certmonger.get_pin('internal') + except IOError, e: + raise RuntimeError('Unable to determine PIN for CA instance: %s' % str(e)) + + # Server-Cert cert-pki-ca is renewed per-server + for nickname in ['auditSigningCert cert-pki-ca', + 'ocspSigningCert cert-pki-ca', + 'subsystemCert cert-pki-ca']: + certmonger.dogtag_start_tracking('dogtag-ipa-renew-agent', nickname, pin, None, '/var/lib/pki-ca/alias', 'renew_ca_cert "%s"' % nickname) + + # Set up the agent cert for renewal + certmonger.dogtag_start_tracking('dogtag-ipa-renew-agent', 'ipaCert', None, '/etc/httpd/alias/pwdfile.txt', '/etc/httpd/alias', 'renew_ra_cert') + + def configure_certmonger_renewal(self): + """ + Create a new CA type for certmonger that will retrieve updated + certificates from the dogtag master server. + """ + target_fname = '/var/lib/certmonger/cas/ca_renewal' + if ipautil.file_exists(target_fname): + # This CA can be configured either during initial CA installation + # if the replica is created with --setup-ca or when Apache is + # being configured if not. + return + txt = ipautil.template_file(ipautil.SHARE_DIR + "ca_renewal", dict()) + fd = open(target_fname, "w") + fd.write(txt) + fd.close() + os.chmod(target_fname, 0600) + ipaservices.restore_context(target_fname) + + cmonger = ipaservices.knownservices.certmonger + cmonger.enable() + ipaservices.knownservices.messagebus.start() + cmonger.restart() + + def configure_clone_renewal(self): + """ + The actual renewal is done on the master. On the clone side we + use a separate certmonger CA that polls LDAP to see if an updated + certificate is available. If it is then it gets installed. + """ + + try: + pin = certmonger.get_pin('internal') + except IOError, e: + raise RuntimeError('Unable to determine PIN for CA instance: %s' % str(e)) + + # Server-Cert cert-pki-ca is renewed per-server + for nickname in ['auditSigningCert cert-pki-ca', + 'ocspSigningCert cert-pki-ca', + 'subsystemCert cert-pki-ca']: + certmonger.dogtag_start_tracking('dogtag-ipa-retrieve-agent-submit', nickname, pin, None, '/var/lib/pki-ca/alias', 'restart_pkicad "%s"' % nickname) + + # The agent renewal is configured in import_ra_cert which is called + # after the HTTP instance is created. + def enable_subject_key_identifier(self): """ See if Subject Key Identifier is set in the profile and if not, add it. @@ -1109,6 +1218,21 @@ class CAInstance(service.Service): # No update was done return False + def is_master(self): + """ + There are some tasks that are only done on a single dogtag master. + By default this is the first one installed. Use this to determine if + that is the case. + + If users have changed their topology so the initial master is either + gone or no longer performing certain duties then it is their + responsibility to handle changes on upgrades. + """ + master = installutils.get_directive( + '/var/lib/pki-ca/conf/CS.cfg', 'subsystem.select', '=') + + return master == 'New' + def install_replica_ca(config, postinstall=False): """ Install a CA on a replica. @@ -1179,6 +1303,25 @@ def install_replica_ca(config, postinstall=False): return (ca, cs) +def update_cert_config(nickname, cert): + """ + When renewing a CA subsystem certificate the configuration file + needs to get the new certificate as well. + + nickname is one of the known nicknames. + cert is a DER-encoded certificate. + """ + # The cert directive to update per nickname + directives = {'auditSigningCert cert-pki-ca': 'ca.audit_signing.cert', + 'ocspSigningCert cert-pki-ca': 'ca.ocsp_signing.cert', + 'caSigningCert cert-pki-ca': 'ca.signing.cert', + 'Server-Cert cert-pki-ca': 'ca.sslserver.cert' } + + installutils.set_directive('/var/lib/%s/conf/CS.cfg' % PKI_INSTANCE_NAME, + directives[nickname], + base64.b64encode(cert), + quotes=False, separator='=') + if __name__ == "__main__": standard_logging_setup("install.log") cs = CADSInstance() diff --git a/selinux/ipa_dogtag/ipa_dogtag.te b/selinux/ipa_dogtag/ipa_dogtag.te index 3750e4d10..1404e17ca 100644 --- a/selinux/ipa_dogtag/ipa_dogtag.te +++ b/selinux/ipa_dogtag/ipa_dogtag.te @@ -1,15 +1,19 @@ -module ipa_dogtag 1.4; +module ipa_dogtag 1.5; require { type httpd_t; type cert_t; type pki_ca_t; type pki_ca_var_lib_t; + type certmonger_t; class dir write; class dir add_name; class dir remove_name; class dir search; class dir getattr; + class file read; + class file getattr; + class file open; class file create; class file write; class file rename; @@ -35,3 +39,7 @@ allow pki_ca_t cert_t:lnk_file unlink; # Let apache read the CRLs allow httpd_t pki_ca_var_lib_t:dir { search getattr }; + +# Let certmonger manage the dogtag certificate database for renewals +allow certmonger_t pki_ca_var_lib_t:dir { search getattr} ; +allow certmonger_t pki_ca_var_lib_t:file { read write getattr open }; |