Hi,
the attached patches fix various bugs and shortcomings in the CA
management and renewal code. Related tickets:
<https://fedorahosted.org/freeipa/ticket/4416>,
<https://fedorahosted.org/freeipa/ticket/4460>.
(Patch 319 was originally posted at
<http://www.redhat.com/archives/freeipa-devel/2014-September/msg00132.html>.)
Honza
--
Jan Cholasta
>From 366b3e5d3fcdf4d003c1ff9ee02543edcd5d96c8 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 18 Sep 2014 16:28:59 +0200
Subject: [PATCH 01/13] Introduce NSS database /etc/ipa/nssdb
---
freeipa.spec.in | 17 ++++
ipa-client/ipa-install/ipa-client-install | 159 ++++++++++++++++++------------
ipa-client/ipaclient/ipa_certupdate.py | 9 ++
ipalib/rpc.py | 2 +-
ipaplatform/base/paths.py | 2 +-
ipapython/certdb.py | 28 ++++++
6 files changed, 154 insertions(+), 63 deletions(-)
diff --git a/freeipa.spec.in b/freeipa.spec.in
index 29ad077..a81c193 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -412,6 +412,7 @@ mkdir -p %{buildroot}/%{_localstatedir}/lib/ipa/backup
mkdir -p %{buildroot}%{_sysconfdir}/ipa/
/bin/touch %{buildroot}%{_sysconfdir}/ipa/default.conf
/bin/touch %{buildroot}%{_sysconfdir}/ipa/ca.crt
+mkdir -p %{buildroot}%{_sysconfdir}/ipa/nssdb
mkdir -p %{buildroot}/%{_localstatedir}/lib/ipa-client/sysrestore
%if ! %{ONLY_CLIENT}
@@ -526,6 +527,17 @@ if [ $1 -gt 1 ] ; then
/bin/systemctl condrestart ntpd.service 2>&1 || :
fi
fi
+
+ if [ ! -f '/etc/ipa/nssdb/cert8.db' -a $restore -ge 2 ]; then
+ python2 -c 'from ipapython.certdb import create_ipa_nssdb; create_ipa_nssdb()' >/dev/null 2>&1
+ tempfile=$(mktemp)
+ if certutil -L -d /etc/pki/nssdb -n 'IPA CA' -a >"$tempfile" 2>/dev/null; then
+ certutil -A -d /etc/ipa/nssdb -f /etc/ipa/nssdb/pwdfile.txt -n 'IPA CA' -t CT,C,C -a -i "$tempfile" >/dev/null
+ elif certutil -L -d /etc/pki/nssdb -n 'External CA cert' -a >"$tempfile" 2>/dev/null; then
+ certutil -A -d /etc/ipa/nssdb -f /etc/ipa/nssdb/pwdfile.txt -n 'External CA cert' -t C,, -a -i "$tempfile" >/dev/null
+ fi
+ rm -f "$tempfile"
+ fi
fi
%triggerin -n freeipa-client -- openssh-server
@@ -785,6 +797,11 @@ fi
%dir %attr(0755,root,root) %{_sysconfdir}/ipa/
%ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/ipa/default.conf
%ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/ipa/ca.crt
+%dir %attr(0755,root,root) %{_sysconfdir}/ipa/nssdb
+%ghost %config(noreplace) %{_sysconfdir}/ipa/nssdb/cert8.db
+%ghost %config(noreplace) %{_sysconfdir}/ipa/nssdb/key3.db
+%ghost %config(noreplace) %{_sysconfdir}/ipa/nssdb/secmod.db
+%ghost %config(noreplace) %{_sysconfdir}/ipa/nssdb/pwdfile.txt
%if ! %{ONLY_CLIENT}
%files tests -f tests-python.list
diff --git a/ipa-client/ipa-install/ipa-client-install b/ipa-client/ipa-install/ipa-client-install
index b3da28d..82e4b18 100755
--- a/ipa-client/ipa-install/ipa-client-install
+++ b/ipa-client/ipa-install/ipa-client-install
@@ -46,7 +46,7 @@ try:
from ipaplatform import services
from ipaplatform.paths import paths
from ipapython import ipautil, sysrestore, version, certmonger, ipaldap
- from ipapython import kernel_keyring
+ from ipapython import kernel_keyring, certdb
from ipapython.config import IPAOptionParser
from ipalib import api, errors
from ipalib import x509, certstore
@@ -550,6 +550,15 @@ def uninstall(options, env):
cmonger.service_name, str(e))
# Remove our host cert and CA cert
+ for filename in (os.path.join(paths.IPA_NSSDB_DIR, 'cert8.db'),
+ os.path.join(paths.IPA_NSSDB_DIR, 'key3.db'),
+ os.path.join(paths.IPA_NSSDB_DIR, 'secmod.db'),
+ os.path.join(paths.IPA_NSSDB_DIR, 'pwdfile.txt')):
+ try:
+ os.remove(filename)
+ except OSError, e:
+ root_logger.error("Failed to remove %s: %s", filename, e)
+
purge_ipa_certs({client_nss_nickname, 'IPA CA', 'External CA cert'})
try:
@@ -2523,62 +2532,71 @@ def install(options, env, fstore, statestore):
except ValueError:
pass
- # Add CA certs to a temporary NSS database
+ tmp_nss_dir = tempfile.mkdtemp()
try:
- os.mkdir(paths.IPA_NSSDB_DIR)
- pwd_file = ipautil.write_tmp_file(ipautil.ipa_generate_password())
- run([paths.CERTUTIL, '-N',
- '-d', paths.IPA_NSSDB_DIR,
- '-f', pwd_file.name])
-
- ca_certs = x509.load_certificate_list_from_file(CACERT)
- ca_certs = [cert.der_data for cert in ca_certs]
- for i, cert in enumerate(ca_certs):
- run([paths.CERTUTIL, '-A',
- '-d', paths.IPA_NSSDB_DIR,
- '-n', 'CA certificate %d' % (i + 1),
- '-t', 'C,,'],
- stdin=cert)
- except CalledProcessError, e:
- root_logger.info("Failed to add CA to temporary NSS database.")
- return CLIENT_INSTALL_ERROR
+ # Add CA certs to a temporary NSS database
+ try:
+ pwd_file = ipautil.write_tmp_file(ipautil.ipa_generate_password())
+ run([paths.CERTUTIL, '-N',
+ '-d', tmp_nss_dir,
+ '-f', pwd_file.name])
- # Now, let's try to connect to the server's XML-RPC interface
- connected = False
- try:
- api.Backend.rpcclient.connect(nss_dir=paths.IPA_NSSDB_DIR)
- connected = True
- root_logger.debug('Try RPC connection')
- api.Backend.rpcclient.forward('ping')
- except errors.KerberosError, e:
- if connected:
- api.Backend.rpcclient.disconnect()
- root_logger.info('Cannot connect to the server due to ' +
- 'Kerberos error: %s. Trying with delegate=True', str(e))
+ ca_certs = x509.load_certificate_list_from_file(CACERT)
+ ca_certs = [cert.der_data for cert in ca_certs]
+ for i, cert in enumerate(ca_certs):
+ run([paths.CERTUTIL, '-A',
+ '-d', tmp_nss_dir,
+ '-n', 'CA certificate %d' % (i + 1),
+ '-t', 'C,,'],
+ stdin=cert)
+ except CalledProcessError, e:
+ root_logger.info("Failed to add CA to temporary NSS database.")
+ return CLIENT_INSTALL_ERROR
+
+ # Now, let's try to connect to the server's XML-RPC interface
+ connected = False
try:
- api.Backend.rpcclient.connect(delegate=True, nss_dir=paths.IPA_NSSDB_DIR)
+ api.Backend.rpcclient.connect(nss_dir=tmp_nss_dir)
+ connected = True
root_logger.debug('Try RPC connection')
api.Backend.rpcclient.forward('ping')
+ except errors.KerberosError, e:
+ if connected:
+ api.Backend.rpcclient.disconnect()
+ root_logger.info('Cannot connect to the server due to ' +
+ 'Kerberos error: %s. Trying with delegate=True', str(e))
+ try:
+ api.Backend.rpcclient.connect(delegate=True,
+ nss_dir=tmp_nss_dir)
+ root_logger.debug('Try RPC connection')
+ api.Backend.rpcclient.forward('ping')
- root_logger.info('Connection with delegate=True successful')
+ root_logger.info('Connection with delegate=True successful')
- # The remote server is not capable of Kerberos S4U2Proxy delegation
- # This features is implemented in IPA server version 2.2 and higher
- root_logger.warning("Target IPA server has a lower version than " +
- "the enrolled client")
- root_logger.warning("Some capabilities including the ipa " +
- "command capability may not be available")
- except errors.PublicError, e2:
- root_logger.warning(
- 'Second connect with delegate=True also failed: %s', str(e2))
+ # The remote server is not capable of Kerberos S4U2Proxy
+ # delegation. This features is implemented in IPA server
+ # version 2.2 and higher
+ root_logger.warning(
+ "Target IPA server has a lower version than the enrolled "
+ "client")
+ root_logger.warning(
+ "Some capabilities including the ipa command capability "
+ "may not be available")
+ except errors.PublicError, e2:
+ root_logger.warning(
+ 'Second connect with delegate=True also failed: %s',
+ str(e2))
+ root_logger.error(
+ "Cannot connect to the IPA server XML-RPC interface: %s",
+ str(e2))
+ return CLIENT_INSTALL_ERROR
+ except errors.PublicError, e:
root_logger.error(
- "Cannot connect to the IPA server XML-RPC interface: %s",
- str(e2))
+ 'Cannot connect to the server due to generic error: %s',
+ str(e))
return CLIENT_INSTALL_ERROR
- except errors.PublicError, e:
- root_logger.error(
- 'Cannot connect to the server due to generic error: %s', str(e))
- return CLIENT_INSTALL_ERROR
+ finally:
+ shutil.rmtree(tmp_nss_dir)
# Use the RPC directly so older servers are supported
result = api.Backend.rpcclient.forward(
@@ -2590,14 +2608,38 @@ def install(options, env, fstore, statestore):
if not remote_env['enable_ra']:
disable_ra()
+ # Create IPA NSS database
+ try:
+ certdb.create_ipa_nssdb()
+ except ipautil.CalledProcessError, e:
+ root_logger.error("Failed to create IPA NSS database: %s", e)
+ return CLIENT_INSTALL_ERROR
+
# Get CA certificates from the certificate store
ca_certs = get_certs_from_ldap(cli_server[0], cli_basedn, cli_realm,
remote_env['enable_ra'])
+ ca_certs_nss = [(c, n, certstore.key_policy_to_trust_flags(t, True, u))
+ for (c, n, t, u) in ca_certs]
- # Add the CA to the platform-dependant systemwide CA store
+ # Add the CA certificates to the IPA NSS database
+ root_logger.debug("Adding CA certificates to the IPA NSS database.")
+ for cert, nickname, trust_flags in ca_certs_nss:
+ try:
+ run([paths.CERTUTIL,
+ "-A",
+ "-d", paths.IPA_NSSDB_DIR,
+ "-n", nickname,
+ "-t", trust_flags],
+ stdin=cert)
+ except CalledProcessError, e:
+ root_logger.error(
+ "Failed to add %s to the IPA NSS database.", nickname)
+ return CLIENT_INSTALL_ERROR
+
+ # Add the CA certificates to the platform-dependant systemwide CA store
tasks.insert_ca_certs_into_systemwide_ca_store(ca_certs)
- # Add the CA to the default NSS database and trust it
+ # Add the CA certificates to the default NSS database
if not purge_ipa_certs():
root_logger.info(
"Failed to remove old IPA certificates from the default NSS "
@@ -2610,12 +2652,10 @@ def install(options, env, fstore, statestore):
root_logger.error("Failed to open /etc/pki/nssdb/ipa.txt: %s", e)
return CLIENT_INSTALL_ERROR
- for cert, nickname, trusted, ext_key_usage in ca_certs:
+ root_logger.debug(
+ "Attempting to add CA certificates to the default NSS database.")
+ for cert, nickname, trust_flags in ca_certs_nss:
try:
- root_logger.debug("Attempting to add CA directly to the "
- "default NSS database.")
- trust_flags = certstore.key_policy_to_trust_flags(
- trusted, True, ext_key_usage)
run([paths.CERTUTIL,
"-A",
"-d", paths.NSS_DB_DIR,
@@ -2623,12 +2663,13 @@ def install(options, env, fstore, statestore):
"-t", trust_flags],
stdin=cert)
except CalledProcessError, e:
- root_logger.info("Failed to add CA to the default NSS database.")
+ root_logger.error(
+ "Failed to add %s to the default NSS database.", nickname)
list_file.close()
return CLIENT_INSTALL_ERROR
else:
- root_logger.info('Added the CA to the default NSS database.')
list_file.write(nickname + '\n')
+ root_logger.info("Added CA certificates to the default NSS database.")
list_file.close()
@@ -2854,7 +2895,3 @@ finally:
os.remove(CCACHE_FILE)
except Exception:
pass
- try:
- shutil.rmtree(paths.IPA_NSSDB_DIR)
- except Exception:
- pass
diff --git a/ipa-client/ipaclient/ipa_certupdate.py b/ipa-client/ipaclient/ipa_certupdate.py
index 8e7fe04..57dbf20 100644
--- a/ipa-client/ipaclient/ipa_certupdate.py
+++ b/ipa-client/ipaclient/ipa_certupdate.py
@@ -70,6 +70,15 @@ class CertUpdate(admintool.AdminTool):
def update_client(self, certs):
self.update_file(paths.IPA_CA_CRT, certs)
+ self.update_db(paths.IPA_NSSDB_DIR, certs)
+
+ for nickname in ('IPA CA', 'External CA cert'):
+ try:
+ ipautil.run([paths.CERTUTIL, '-D',
+ '-d', paths.NSS_DB_DIR,
+ '-n', nickname])
+ except ipautil.CalledProcessError, e:
+ pass
self.update_db(paths.NSS_DB_DIR, certs)
diff --git a/ipalib/rpc.py b/ipalib/rpc.py
index 1bfc4c3..7b966bd 100644
--- a/ipalib/rpc.py
+++ b/ipalib/rpc.py
@@ -483,7 +483,7 @@ class SSLTransport(LanguageAwareTransport):
if self._connection and host == self._connection[0]:
return self._connection[1]
- dbdir = getattr(context, 'nss_dir', paths.NSS_DB_DIR)
+ dbdir = getattr(context, 'nss_dir', paths.IPA_NSSDB_DIR)
no_init = self.__nss_initialized(dbdir)
if sys.version_info < (2, 7):
conn = NSSHTTPS(host, 443, dbdir=dbdir, no_init=no_init)
diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py
index 6f2a29e..1493918 100644
--- a/ipaplatform/base/paths.py
+++ b/ipaplatform/base/paths.py
@@ -63,7 +63,7 @@ class BasePathNamespace(object):
IPA_DNS_UPDATE_TXT = "/etc/ipa/.dns_update.txt"
IPA_CA_CRT = "/etc/ipa/ca.crt"
IPA_DEFAULT_CONF = "/etc/ipa/default.conf"
- IPA_NSSDB_DIR = "/etc/ipa/.nssdb"
+ IPA_NSSDB_DIR = "/etc/ipa/nssdb"
KRB5_CONF = "/etc/krb5.conf"
KRB5_KEYTAB = "/etc/krb5.keytab"
LDAP_CONF = "/etc/ldap.conf"
diff --git a/ipapython/certdb.py b/ipapython/certdb.py
index a858313..426c809 100644
--- a/ipapython/certdb.py
+++ b/ipapython/certdb.py
@@ -17,6 +17,34 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
+import os
+
+from ipaplatform.paths import paths
+from ipapython import ipautil
+
CA_NICKNAME_FMT = "%s IPA CA"
+
+
def get_ca_nickname(realm, format=CA_NICKNAME_FMT):
return format % realm
+
+
+def create_ipa_nssdb():
+ pwdfile = os.path.join(paths.IPA_NSSDB_DIR, 'pwdfile.txt')
+
+ ipautil.backup_file(pwdfile)
+ ipautil.backup_file(os.path.join(paths.IPA_NSSDB_DIR, 'cert8.db'))
+ ipautil.backup_file(os.path.join(paths.IPA_NSSDB_DIR, 'key3.db'))
+ ipautil.backup_file(os.path.join(paths.IPA_NSSDB_DIR, 'secmod.db'))
+
+ with open(pwdfile, 'w') as f:
+ f.write(ipautil.ipa_generate_password(pwd_len=40))
+ os.chmod(pwdfile, 0600)
+
+ ipautil.run([paths.CERTUTIL,
+ "-N",
+ "-d", paths.IPA_NSSDB_DIR,
+ "-f", pwdfile])
+ os.chmod(os.path.join(paths.IPA_NSSDB_DIR, 'cert8.db'), 0644)
+ os.chmod(os.path.join(paths.IPA_NSSDB_DIR, 'key3.db'), 0644)
+ os.chmod(os.path.join(paths.IPA_NSSDB_DIR, 'secmod.db'), 0644)
--
1.9.3
>From 2ea4763ea43db715359c27c97c12dcfaed78ffda Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 18 Sep 2014 11:19:46 +0200
Subject: [PATCH 02/13] Move NSSDatabase from ipaserver.certs to
ipapython.certdb
https://fedorahosted.org/freeipa/ticket/4416
---
ipapython/certdb.py | 256 +++++++++++++++++++++++++++++++++++++++++++++
ipaserver/install/certs.py | 254 +-------------------------------------------
2 files changed, 257 insertions(+), 253 deletions(-)
diff --git a/ipapython/certdb.py b/ipapython/certdb.py
index 426c809..9c1dfc4 100644
--- a/ipapython/certdb.py
+++ b/ipapython/certdb.py
@@ -18,8 +18,14 @@
#
import os
+import re
+import tempfile
+import shutil
+from nss import nss
+from nss.error import NSPRError
from ipaplatform.paths import paths
+from ipapython.ipa_log_manager import root_logger
from ipapython import ipautil
CA_NICKNAME_FMT = "%s IPA CA"
@@ -48,3 +54,253 @@ def create_ipa_nssdb():
os.chmod(os.path.join(paths.IPA_NSSDB_DIR, 'cert8.db'), 0644)
os.chmod(os.path.join(paths.IPA_NSSDB_DIR, 'key3.db'), 0644)
os.chmod(os.path.join(paths.IPA_NSSDB_DIR, 'secmod.db'), 0644)
+
+
+def find_cert_from_txt(cert, start=0):
+ """
+ Given a cert blob (str) which may or may not contian leading and
+ trailing text, pull out just the certificate part. This will return
+ the FIRST cert in a stream of data.
+
+ Returns a tuple (certificate, last position in cert)
+ """
+ s = cert.find('-----BEGIN CERTIFICATE-----', start)
+ e = cert.find('-----END CERTIFICATE-----', s)
+ if e > 0:
+ e = e + 25
+
+ if s < 0 or e < 0:
+ raise RuntimeError("Unable to find certificate")
+
+ cert = cert[s:e]
+ return (cert, e)
+
+
+class NSSDatabase(object):
+ """A general-purpose wrapper around a NSS cert database
+
+ For permanent NSS databases, pass the cert DB directory to __init__
+
+ For temporary databases, do not pass nssdir, and call close() when done
+ to remove the DB. Alternatively, a NSSDatabase can be used as a
+ context manager that calls close() automatically.
+ """
+ # Traditionally, we used CertDB for our NSS DB operations, but that class
+ # got too tied to IPA server details, killing reusability.
+ # BaseCertDB is a class that knows nothing about IPA.
+ # Generic NSS DB code should be moved here.
+ def __init__(self, nssdir=None):
+ if nssdir is None:
+ self.secdir = tempfile.mkdtemp()
+ self._is_temporary = True
+ else:
+ self.secdir = nssdir
+ self._is_temporary = False
+
+ def close(self):
+ if self._is_temporary:
+ shutil.rmtree(self.secdir)
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, value, tb):
+ self.close()
+
+ def run_certutil(self, args, stdin=None):
+ new_args = [paths.CERTUTIL, "-d", self.secdir]
+ new_args = new_args + args
+ return ipautil.run(new_args, stdin)
+
+ def create_db(self, password_filename):
+ """Create cert DB
+
+ :param password_filename: Name of file containing the database password
+ """
+ self.run_certutil(["-N", "-f", password_filename])
+
+ def list_certs(self):
+ """Return nicknames and cert flags for all certs in the database
+
+ :return: List of (name, trust_flags) tuples
+ """
+ certs, stderr, returncode = self.run_certutil(["-L"])
+ certs = certs.splitlines()
+
+ # FIXME, this relies on NSS never changing the formatting of certutil
+ certlist = []
+ for cert in certs:
+ match = re.match(r'^(.+?)\s+(\w*,\w*,\w*)\s*$', cert)
+ if match:
+ certlist.append(match.groups())
+
+ return tuple(certlist)
+
+ def find_server_certs(self):
+ """Return nicknames and cert flags for server certs in the database
+
+ Server certs have an "u" character in the trust flags.
+
+ :return: List of (name, trust_flags) tuples
+ """
+ server_certs = []
+ for name, flags in self.list_certs():
+ if 'u' in flags:
+ server_certs.append((name, flags))
+
+ return server_certs
+
+ def get_trust_chain(self, nickname):
+ """Return names of certs in a given cert's trust chain
+
+ :param nickname: Name of the cert
+ :return: List of certificate names
+ """
+ root_nicknames = []
+ chain, stderr, returncode = self.run_certutil([
+ "-O", "-n", nickname])
+ chain = chain.splitlines()
+
+ for c in chain:
+ m = re.match('\s*"(.*)" \[.*', c)
+ if m:
+ root_nicknames.append(m.groups()[0])
+
+ return root_nicknames
+
+ def import_pkcs12(self, pkcs12_filename, db_password_filename,
+ pkcs12_passwd=None):
+ args = [paths.PK12UTIL, "-d", self.secdir,
+ "-i", pkcs12_filename,
+ "-k", db_password_filename, '-v']
+ if pkcs12_passwd is not None:
+ pkcs12_passwd = pkcs12_passwd + '\n'
+ args = args + ["-w", paths.DEV_STDIN]
+ try:
+ ipautil.run(args, stdin=pkcs12_passwd)
+ except ipautil.CalledProcessError, e:
+ if e.returncode == 17:
+ raise RuntimeError("incorrect password for pkcs#12 file %s" %
+ pkcs12_filename)
+ elif e.returncode == 10:
+ raise RuntimeError("Failed to open %s" % pkcs12_filename)
+ else:
+ raise RuntimeError("unknown error import pkcs#12 file %s" %
+ pkcs12_filename)
+
+ def trust_root_cert(self, root_nickname, trust_flags=None):
+ if root_nickname[:7] == "Builtin":
+ root_logger.debug(
+ "No need to add trust for built-in root CAs, skipping %s" %
+ root_nickname)
+ else:
+ if trust_flags is None:
+ trust_flags = 'C,,'
+ try:
+ self.run_certutil(["-M", "-n", root_nickname,
+ "-t", trust_flags])
+ except ipautil.CalledProcessError, e:
+ raise RuntimeError(
+ "Setting trust on %s failed" % root_nickname)
+
+ def get_cert(self, nickname, pem=False):
+ args = ['-L', '-n', nickname]
+ if pem:
+ args.append('-a')
+ else:
+ args.append('-r')
+ try:
+ cert, err, returncode = self.run_certutil(args)
+ except ipautil.CalledProcessError:
+ raise RuntimeError("Failed to get %s" % nickname)
+ return cert
+
+ def export_pem_cert(self, nickname, location):
+ """Export the given cert to PEM file in the given location"""
+ cert = self.get_cert(nickname)
+ with open(location, "w+") as fd:
+ fd.write(cert)
+ os.chmod(location, 0444)
+
+ def import_pem_cert(self, nickname, flags, location):
+ """Import a cert form the given PEM file.
+
+ The file must contain exactly one certificate.
+ """
+ try:
+ with open(location) as fd:
+ certs = fd.read()
+ except IOError as e:
+ raise RuntimeError(
+ "Failed to open %s: %s" % (location, e.strerror)
+ )
+
+ cert, st = find_cert_from_txt(certs)
+ self.add_cert(cert, nickname, flags, pem=True)
+
+ try:
+ find_cert_from_txt(certs, st)
+ except RuntimeError:
+ pass
+ else:
+ raise ValueError('%s contains more than one certificate' %
+ location)
+
+ def add_cert(self, cert, nick, flags, pem=False):
+ args = ["-A", "-n", nick, "-t", flags]
+ if pem:
+ args.append("-a")
+ self.run_certutil(args, stdin=cert)
+
+ def delete_cert(self, nick):
+ self.run_certutil(["-D", "-n", nick])
+
+ def verify_server_cert_validity(self, nickname, hostname):
+ """Verify a certificate is valid for a SSL server with given hostname
+
+ Raises a ValueError if the certificate is invalid.
+ """
+ certdb = cert = None
+ if nss.nss_is_initialized():
+ nss.nss_shutdown()
+ nss.nss_init(self.secdir)
+ try:
+ certdb = nss.get_default_certdb()
+ cert = nss.find_cert_from_nickname(nickname)
+ intended_usage = nss.certificateUsageSSLServer
+ try:
+ approved_usage = cert.verify_now(certdb, True, intended_usage)
+ except NSPRError, e:
+ if e.errno != -8102:
+ raise ValueError(e.strerror)
+ approved_usage = 0
+ if not approved_usage & intended_usage:
+ raise ValueError('invalid for a SSL server')
+ if not cert.verify_hostname(hostname):
+ raise ValueError('invalid for server %s' % hostname)
+ finally:
+ del certdb, cert
+ nss.nss_shutdown()
+
+ return None
+
+ def verify_ca_cert_validity(self, nickname):
+ certdb = cert = None
+ if nss.nss_is_initialized():
+ nss.nss_shutdown()
+ nss.nss_init(self.secdir)
+ try:
+ certdb = nss.get_default_certdb()
+ cert = nss.find_cert_from_nickname(nickname)
+ intended_usage = nss.certificateUsageSSLCA
+ try:
+ approved_usage = cert.verify_now(certdb, True, intended_usage)
+ except NSPRError, e:
+ if e.errno != -8102: # SEC_ERROR_INADEQUATE_KEY_USAGE
+ raise ValueError(e.strerror)
+ approved_usage = 0
+ if approved_usage & intended_usage != intended_usage:
+ raise ValueError('invalid for a CA')
+ finally:
+ del certdb, cert
+ nss.nss_shutdown()
diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py
index 4d508cd..c2ab59e 100644
--- a/ipaserver/install/certs.py
+++ b/ipaserver/install/certs.py
@@ -19,7 +19,6 @@
import os
import stat
-import re
import sys
import tempfile
import shutil
@@ -28,15 +27,12 @@ import pwd
import base64
from hashlib import sha1
-from nss import nss
-from nss.error import NSPRError
-
from ipapython.ipa_log_manager import root_logger
from ipapython import dogtag
from ipapython import sysrestore
from ipapython import ipautil
from ipapython import certmonger
-from ipapython.certdb import get_ca_nickname
+from ipapython.certdb import get_ca_nickname, find_cert_from_txt, NSSDatabase
from ipapython.dn import DN
from ipalib import pkcs10, x509, api
from ipalib.errors import CertificateOperationError
@@ -48,24 +44,6 @@ from ipaplatform.paths import paths
# where apache can reach
NSS_DIR = paths.HTTPD_ALIAS_DIR
-def find_cert_from_txt(cert, start=0):
- """
- Given a cert blob (str) which may or may not contian leading and
- trailing text, pull out just the certificate part. This will return
- the FIRST cert in a stream of data.
-
- Returns a tuple (certificate, last position in cert)
- """
- s = cert.find('-----BEGIN CERTIFICATE-----', start)
- e = cert.find('-----END CERTIFICATE-----', s)
- if e > 0: e = e + 25
-
- if s < 0 or e < 0:
- raise RuntimeError("Unable to find certificate")
-
- cert = cert[s:e]
- return (cert, e)
-
def get_cert_nickname(cert):
"""
Using the subject from cert come up with a nickname suitable
@@ -83,236 +61,6 @@ def get_cert_nickname(cert):
return (str(dn[0]), dn)
-class NSSDatabase(object):
- """A general-purpose wrapper around a NSS cert database
-
- For permanent NSS databases, pass the cert DB directory to __init__
-
- For temporary databases, do not pass nssdir, and call close() when done
- to remove the DB. Alternatively, a NSSDatabase can be used as a
- context manager that calls close() automatically.
- """
- # Traditionally, we used CertDB for our NSS DB operations, but that class
- # got too tied to IPA server details, killing reusability.
- # BaseCertDB is a class that knows nothing about IPA.
- # Generic NSS DB code should be moved here.
- def __init__(self, nssdir=None):
- if nssdir is None:
- self.secdir = tempfile.mkdtemp()
- self._is_temporary = True
- else:
- self.secdir = nssdir
- self._is_temporary = False
-
- def close(self):
- if self._is_temporary:
- shutil.rmtree(self.secdir)
-
- def __enter__(self):
- return self
-
- def __exit__(self, type, value, tb):
- self.close()
-
- def run_certutil(self, args, stdin=None):
- new_args = [paths.CERTUTIL, "-d", self.secdir]
- new_args = new_args + args
- return ipautil.run(new_args, stdin)
-
- def create_db(self, password_filename):
- """Create cert DB
-
- :param password_filename: Name of file containing the database password
- """
- self.run_certutil(["-N", "-f", password_filename])
-
- def list_certs(self):
- """Return nicknames and cert flags for all certs in the database
-
- :return: List of (name, trust_flags) tuples
- """
- certs, stderr, returncode = self.run_certutil(["-L"])
- certs = certs.splitlines()
-
- # FIXME, this relies on NSS never changing the formatting of certutil
- certlist = []
- for cert in certs:
- match = re.match(r'^(.+?)\s+(\w*,\w*,\w*)\s*$', cert)
- if match:
- certlist.append(match.groups())
-
- return tuple(certlist)
-
- def find_server_certs(self):
- """Return nicknames and cert flags for server certs in the database
-
- Server certs have an "u" character in the trust flags.
-
- :return: List of (name, trust_flags) tuples
- """
- server_certs = []
- for name, flags in self.list_certs():
- if 'u' in flags:
- server_certs.append((name, flags))
-
- return server_certs
-
- def get_trust_chain(self, nickname):
- """Return names of certs in a given cert's trust chain
-
- :param nickname: Name of the cert
- :return: List of certificate names
- """
- root_nicknames = []
- chain, stderr, returncode = self.run_certutil([
- "-O", "-n", nickname])
- chain = chain.splitlines()
-
- for c in chain:
- m = re.match('\s*"(.*)" \[.*', c)
- if m:
- root_nicknames.append(m.groups()[0])
-
- return root_nicknames
-
- def import_pkcs12(self, pkcs12_filename, db_password_filename,
- pkcs12_passwd=None):
- args = [paths.PK12UTIL, "-d", self.secdir,
- "-i", pkcs12_filename,
- "-k", db_password_filename, '-v']
- if pkcs12_passwd is not None:
- pkcs12_passwd = pkcs12_passwd + '\n'
- args = args + ["-w", paths.DEV_STDIN]
- try:
- ipautil.run(args, stdin=pkcs12_passwd)
- except ipautil.CalledProcessError, e:
- if e.returncode == 17:
- raise RuntimeError("incorrect password for pkcs#12 file %s" %
- pkcs12_filename)
- elif e.returncode == 10:
- raise RuntimeError("Failed to open %s" % pkcs12_filename)
- else:
- raise RuntimeError("unknown error import pkcs#12 file %s" %
- pkcs12_filename)
-
- def trust_root_cert(self, root_nickname, trust_flags=None):
- if root_nickname[:7] == "Builtin":
- root_logger.debug(
- "No need to add trust for built-in root CAs, skipping %s" %
- root_nickname)
- else:
- if trust_flags is None:
- trust_flags = 'C,,'
- try:
- self.run_certutil(["-M", "-n", root_nickname,
- "-t", trust_flags])
- except ipautil.CalledProcessError, e:
- raise RuntimeError(
- "Setting trust on %s failed" % root_nickname)
-
- def get_cert(self, nickname, pem=False):
- args = ['-L', '-n', nickname]
- if pem:
- args.append('-a')
- else:
- args.append('-r')
- try:
- cert, err, returncode = self.run_certutil(args)
- except ipautil.CalledProcessError:
- raise RuntimeError("Failed to get %s" % nickname)
- return cert
-
- def export_pem_cert(self, nickname, location):
- """Export the given cert to PEM file in the given location"""
- cert = self.get_cert(nickname)
- with open(location, "w+") as fd:
- fd.write(cert)
- os.chmod(location, 0444)
-
- def import_pem_cert(self, nickname, flags, location):
- """Import a cert form the given PEM file.
-
- The file must contain exactly one certificate.
- """
- try:
- with open(location) as fd:
- certs = fd.read()
- except IOError as e:
- raise RuntimeError(
- "Failed to open %s: %s" % (location, e.strerror)
- )
-
- cert, st = find_cert_from_txt(certs)
- self.add_cert(cert, nickname, flags, pem=True)
-
- try:
- find_cert_from_txt(certs, st)
- except RuntimeError:
- pass
- else:
- raise ValueError('%s contains more than one certificate' %
- location)
-
- def add_cert(self, cert, nick, flags, pem=False):
- args = ["-A", "-n", nick, "-t", flags]
- if pem:
- args.append("-a")
- self.run_certutil(args, stdin=cert)
-
- def delete_cert(self, nick):
- self.run_certutil(["-D", "-n", nick])
-
- def verify_server_cert_validity(self, nickname, hostname):
- """Verify a certificate is valid for a SSL server with given hostname
-
- Raises a ValueError if the certificate is invalid.
- """
- certdb = cert = None
- if nss.nss_is_initialized():
- nss.nss_shutdown()
- nss.nss_init(self.secdir)
- try:
- certdb = nss.get_default_certdb()
- cert = nss.find_cert_from_nickname(nickname)
- intended_usage = nss.certificateUsageSSLServer
- try:
- approved_usage = cert.verify_now(certdb, True, intended_usage)
- except NSPRError, e:
- if e.errno != -8102:
- raise ValueError(e.strerror)
- approved_usage = 0
- if not approved_usage & intended_usage:
- raise ValueError('invalid for a SSL server')
- if not cert.verify_hostname(hostname):
- raise ValueError('invalid for server %s' % hostname)
- finally:
- del certdb, cert
- nss.nss_shutdown()
-
- return None
-
- def verify_ca_cert_validity(self, nickname):
- certdb = cert = None
- if nss.nss_is_initialized():
- nss.nss_shutdown()
- nss.nss_init(self.secdir)
- try:
- certdb = nss.get_default_certdb()
- cert = nss.find_cert_from_nickname(nickname)
- intended_usage = nss.certificateUsageSSLCA
- try:
- approved_usage = cert.verify_now(certdb, True, intended_usage)
- except NSPRError, e:
- if e.errno != -8102: # SEC_ERROR_INADEQUATE_KEY_USAGE
- raise ValueError(e.strerror)
- approved_usage = 0
- if approved_usage & intended_usage != intended_usage:
- raise ValueError('invalid for a CA')
- finally:
- del certdb, cert
- nss.nss_shutdown()
-
-
class CertDB(object):
"""An IPA-server-specific wrapper around NSS
--
1.9.3
>From efb446d960f18ec443025bd16ff99ca2edd4a36b Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 18 Sep 2014 11:42:14 +0200
Subject: [PATCH 03/13] Add NSSDatabase.has_nickname for checking nickname
presence in a NSS DB
---
ipapython/certdb.py | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/ipapython/certdb.py b/ipapython/certdb.py
index 9c1dfc4..fc09919 100644
--- a/ipapython/certdb.py
+++ b/ipapython/certdb.py
@@ -215,6 +215,14 @@ class NSSDatabase(object):
raise RuntimeError("Failed to get %s" % nickname)
return cert
+ def has_nickname(self, nickname):
+ try:
+ self.get_cert(nickname)
+ except RuntimeError:
+ return False
+ else:
+ return True
+
def export_pem_cert(self, nickname, location):
"""Export the given cert to PEM file in the given location"""
cert = self.get_cert(nickname)
--
1.9.3
>From bf9414edde0d117d8e1a1cc8c9233f3a486f421d Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 18 Sep 2014 12:00:15 +0200
Subject: [PATCH 04/13] Use NSSDatabase instead of direct certutil calls in
client code
---
ipa-client/ipa-install/ipa-client-install | 50 ++++++++-----------------------
ipa-client/ipaclient/ipa_certupdate.py | 14 ++++-----
ipapython/certdb.py | 20 ++++++-------
3 files changed, 26 insertions(+), 58 deletions(-)
diff --git a/ipa-client/ipa-install/ipa-client-install b/ipa-client/ipa-install/ipa-client-install
index 82e4b18..85f7c8d 100755
--- a/ipa-client/ipa-install/ipa-client-install
+++ b/ipa-client/ipa-install/ipa-client-install
@@ -226,14 +226,6 @@ def logging_setup(options):
def log_service_error(name, action, error):
root_logger.error("%s failed to %s: %s", name, action, str(error))
-def nickname_exists(nickname):
- (sout, serr, returncode) = run([paths.CERTUTIL, "-L", "-d", paths.NSS_DB_DIR, "-n", nickname], raiseonerr=False)
-
- if returncode == 0:
- return True
- else:
- return False
-
def purge_ipa_certs(additional=[]):
filename = paths.NSSDB_IPA_TXT
if file_exists(filename):
@@ -258,12 +250,11 @@ def purge_ipa_certs(additional=[]):
if nickname:
nicknames.add(nickname)
+ sys_db = certdb.NSSDatabase(paths.NSS_DB_DIR)
for nickname in nicknames:
- while nickname_exists(nickname):
+ while sys_db.has_nickname(nickname):
try:
- run([paths.CERTUTIL, "-D",
- "-d", paths.NSS_DB_DIR,
- "-n", nickname])
+ sys_db.delete_cert(nickname)
except Exception, e:
root_logger.error(
"Failed to remove %s from /etc/pki/nssdb: %s", nickname, e)
@@ -2532,23 +2523,16 @@ def install(options, env, fstore, statestore):
except ValueError:
pass
- tmp_nss_dir = tempfile.mkdtemp()
- try:
+ with certdb.NSSDatabase() as tmp_db:
# Add CA certs to a temporary NSS database
try:
pwd_file = ipautil.write_tmp_file(ipautil.ipa_generate_password())
- run([paths.CERTUTIL, '-N',
- '-d', tmp_nss_dir,
- '-f', pwd_file.name])
+ tmp_db.create_db(pwd_file.name)
ca_certs = x509.load_certificate_list_from_file(CACERT)
ca_certs = [cert.der_data for cert in ca_certs]
for i, cert in enumerate(ca_certs):
- run([paths.CERTUTIL, '-A',
- '-d', tmp_nss_dir,
- '-n', 'CA certificate %d' % (i + 1),
- '-t', 'C,,'],
- stdin=cert)
+ tmp_db.add_cert(cert, 'CA certificate %d' % (i + 1), 'C,,')
except CalledProcessError, e:
root_logger.info("Failed to add CA to temporary NSS database.")
return CLIENT_INSTALL_ERROR
@@ -2556,7 +2540,7 @@ def install(options, env, fstore, statestore):
# Now, let's try to connect to the server's XML-RPC interface
connected = False
try:
- api.Backend.rpcclient.connect(nss_dir=tmp_nss_dir)
+ api.Backend.rpcclient.connect(nss_dir=tmp_db.secdir)
connected = True
root_logger.debug('Try RPC connection')
api.Backend.rpcclient.forward('ping')
@@ -2567,7 +2551,7 @@ def install(options, env, fstore, statestore):
'Kerberos error: %s. Trying with delegate=True', str(e))
try:
api.Backend.rpcclient.connect(delegate=True,
- nss_dir=tmp_nss_dir)
+ nss_dir=tmp_db.secdir)
root_logger.debug('Try RPC connection')
api.Backend.rpcclient.forward('ping')
@@ -2595,8 +2579,6 @@ def install(options, env, fstore, statestore):
'Cannot connect to the server due to generic error: %s',
str(e))
return CLIENT_INSTALL_ERROR
- finally:
- shutil.rmtree(tmp_nss_dir)
# Use the RPC directly so older servers are supported
result = api.Backend.rpcclient.forward(
@@ -2623,14 +2605,10 @@ def install(options, env, fstore, statestore):
# Add the CA certificates to the IPA NSS database
root_logger.debug("Adding CA certificates to the IPA NSS database.")
+ ipa_db = certdb.NSSDatabase(paths.IPA_NSSDB_DIR)
for cert, nickname, trust_flags in ca_certs_nss:
try:
- run([paths.CERTUTIL,
- "-A",
- "-d", paths.IPA_NSSDB_DIR,
- "-n", nickname,
- "-t", trust_flags],
- stdin=cert)
+ ipa_db.add_cert(cert, nickname, trust_flags)
except CalledProcessError, e:
root_logger.error(
"Failed to add %s to the IPA NSS database.", nickname)
@@ -2654,14 +2632,10 @@ def install(options, env, fstore, statestore):
root_logger.debug(
"Attempting to add CA certificates to the default NSS database.")
+ sys_db = certdb.NSSDatabase(paths.NSS_DB_DIR)
for cert, nickname, trust_flags in ca_certs_nss:
try:
- run([paths.CERTUTIL,
- "-A",
- "-d", paths.NSS_DB_DIR,
- "-n", nickname,
- "-t", trust_flags],
- stdin=cert)
+ sys_db.add_cert(cert, nickname, trust_flags)
except CalledProcessError, e:
root_logger.error(
"Failed to add %s to the default NSS database.", nickname)
diff --git a/ipa-client/ipaclient/ipa_certupdate.py b/ipa-client/ipaclient/ipa_certupdate.py
index 57dbf20..f7b0e29 100644
--- a/ipa-client/ipaclient/ipa_certupdate.py
+++ b/ipa-client/ipaclient/ipa_certupdate.py
@@ -22,7 +22,7 @@ import tempfile
import shutil
from ipapython import (admintool, ipautil, ipaldap, sysrestore, dogtag,
- certmonger)
+ certmonger, certdb)
from ipaplatform import services
from ipaplatform.paths import paths
from ipaplatform.tasks import tasks
@@ -72,11 +72,10 @@ class CertUpdate(admintool.AdminTool):
self.update_file(paths.IPA_CA_CRT, certs)
self.update_db(paths.IPA_NSSDB_DIR, certs)
+ sys_db = certdb.NSSDatabase(paths.NSS_DB_DIR)
for nickname in ('IPA CA', 'External CA cert'):
try:
- ipautil.run([paths.CERTUTIL, '-D',
- '-d', paths.NSS_DB_DIR,
- '-n', nickname])
+ sys_db.delete_cert(nickname)
except ipautil.CalledProcessError, e:
pass
@@ -165,15 +164,12 @@ class CertUpdate(admintool.AdminTool):
self.log.error("failed to update %s: %s", filename, e)
def update_db(self, path, certs):
+ db = certdb.NSSDatabase(path)
for cert, nickname, trusted, eku in certs:
trust_flags = certstore.key_policy_to_trust_flags(
trusted, True, eku)
try:
- ipautil.run([paths.CERTUTIL, '-A',
- '-d', path,
- '-n', nickname,
- '-t', trust_flags],
- stdin=cert)
+ db.add_cert(cert, nickname, trust_flags)
except ipautil.CalledProcessError, e:
self.log.error(
"failed to update %s in %s: %s", nickname, path, e)
diff --git a/ipapython/certdb.py b/ipapython/certdb.py
index fc09919..e6c042e 100644
--- a/ipapython/certdb.py
+++ b/ipapython/certdb.py
@@ -36,24 +36,22 @@ def get_ca_nickname(realm, format=CA_NICKNAME_FMT):
def create_ipa_nssdb():
- pwdfile = os.path.join(paths.IPA_NSSDB_DIR, 'pwdfile.txt')
+ db = NSSDatabase(paths.IPA_NSSDB_DIR)
+ pwdfile = os.path.join(db.secdir, 'pwdfile.txt')
ipautil.backup_file(pwdfile)
- ipautil.backup_file(os.path.join(paths.IPA_NSSDB_DIR, 'cert8.db'))
- ipautil.backup_file(os.path.join(paths.IPA_NSSDB_DIR, 'key3.db'))
- ipautil.backup_file(os.path.join(paths.IPA_NSSDB_DIR, 'secmod.db'))
+ ipautil.backup_file(os.path.join(db.secdir, 'cert8.db'))
+ ipautil.backup_file(os.path.join(db.secdir, 'key3.db'))
+ ipautil.backup_file(os.path.join(db.secdir, 'secmod.db'))
with open(pwdfile, 'w') as f:
f.write(ipautil.ipa_generate_password(pwd_len=40))
os.chmod(pwdfile, 0600)
- ipautil.run([paths.CERTUTIL,
- "-N",
- "-d", paths.IPA_NSSDB_DIR,
- "-f", pwdfile])
- os.chmod(os.path.join(paths.IPA_NSSDB_DIR, 'cert8.db'), 0644)
- os.chmod(os.path.join(paths.IPA_NSSDB_DIR, 'key3.db'), 0644)
- os.chmod(os.path.join(paths.IPA_NSSDB_DIR, 'secmod.db'), 0644)
+ db.create_db(pwdfile)
+ os.chmod(os.path.join(db.secdir, 'cert8.db'), 0644)
+ os.chmod(os.path.join(db.secdir, 'key3.db'), 0644)
+ os.chmod(os.path.join(db.secdir, 'secmod.db'), 0644)
def find_cert_from_txt(cert, start=0):
--
1.9.3
>From 3c9913a4e0ba4d0a4da60c7ebbe6497c63e8fa4e Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Mon, 22 Sep 2014 11:13:15 +0200
Subject: [PATCH 05/13] Use /etc/ipa/nssdb to get nicknames of IPA certs
installed in /etc/pki/nssdb
---
ipa-client/ipa-install/ipa-client-install | 78 +++++++++----------------------
ipa-client/ipaclient/ipa_certupdate.py | 60 +++++++++---------------
ipaplatform/base/paths.py | 1 -
3 files changed, 43 insertions(+), 96 deletions(-)
diff --git a/ipa-client/ipa-install/ipa-client-install b/ipa-client/ipa-install/ipa-client-install
index 85f7c8d..53678d2 100755
--- a/ipa-client/ipa-install/ipa-client-install
+++ b/ipa-client/ipa-install/ipa-client-install
@@ -226,41 +226,6 @@ def logging_setup(options):
def log_service_error(name, action, error):
root_logger.error("%s failed to %s: %s", name, action, str(error))
-def purge_ipa_certs(additional=[]):
- filename = paths.NSSDB_IPA_TXT
- if file_exists(filename):
- try:
- with open(filename, 'r') as f:
- lines = f.readlines()
- except IOError, e:
- root_logger.error("Failed to open %s: %s", filename, e)
- return False
- finally:
- try:
- os.unlink(filename)
- except OSError, e:
- root_logger.error("Failed to remove %s: %s", filename, e)
- return False
- else:
- lines = []
-
- nicknames = set(additional)
- for line in lines:
- nickname = line.strip()
- if nickname:
- nicknames.add(nickname)
-
- sys_db = certdb.NSSDatabase(paths.NSS_DB_DIR)
- for nickname in nicknames:
- while sys_db.has_nickname(nickname):
- try:
- sys_db.delete_cert(nickname)
- except Exception, e:
- root_logger.error(
- "Failed to remove %s from /etc/pki/nssdb: %s", nickname, e)
-
- return True
-
def cert_summary(msg, certs, indent=' '):
if msg:
s = '%s\n' % msg
@@ -541,16 +506,32 @@ def uninstall(options, env):
cmonger.service_name, str(e))
# Remove our host cert and CA cert
- for filename in (os.path.join(paths.IPA_NSSDB_DIR, 'cert8.db'),
- os.path.join(paths.IPA_NSSDB_DIR, 'key3.db'),
- os.path.join(paths.IPA_NSSDB_DIR, 'secmod.db'),
- os.path.join(paths.IPA_NSSDB_DIR, 'pwdfile.txt')):
+ ipa_db = certdb.NSSDatabase(paths.IPA_NSSDB_DIR)
+ try:
+ ipa_certs = ipa_db.list_certs()
+ except CalledProcessError, e:
+ root_logger.error(
+ "Failed to list certificates in %s: %s", ipa_db.secdir, e)
+ ipa_certs = []
+
+ for filename in (os.path.join(ipa_db.secdir, 'cert8.db'),
+ os.path.join(ipa_db.secdir, 'key3.db'),
+ os.path.join(ipa_db.secdir, 'secmod.db'),
+ os.path.join(ipa_db.secdir, 'pwdfile.txt')):
try:
os.remove(filename)
except OSError, e:
root_logger.error("Failed to remove %s: %s", filename, e)
- purge_ipa_certs({client_nss_nickname, 'IPA CA', 'External CA cert'})
+ sys_db = certdb.NSSDatabase(paths.NSS_DB_DIR)
+ for nickname, trust_flags in ipa_certs:
+ while sys_db.has_nickname(nickname):
+ try:
+ sys_db.delete_cert(nickname)
+ except Exception, e:
+ root_logger.error("Failed to remove %s from %s: %s",
+ nickname, sys_db.secdir, e)
+ break
try:
cmonger.stop()
@@ -2618,18 +2599,6 @@ def install(options, env, fstore, statestore):
tasks.insert_ca_certs_into_systemwide_ca_store(ca_certs)
# Add the CA certificates to the default NSS database
- if not purge_ipa_certs():
- root_logger.info(
- "Failed to remove old IPA certificates from the default NSS "
- "database.")
- return CLIENT_INSTALL_ERROR
-
- try:
- list_file = open(paths.NSSDB_IPA_TXT, 'w')
- except IOError, e:
- root_logger.error("Failed to open /etc/pki/nssdb/ipa.txt: %s", e)
- return CLIENT_INSTALL_ERROR
-
root_logger.debug(
"Attempting to add CA certificates to the default NSS database.")
sys_db = certdb.NSSDatabase(paths.NSS_DB_DIR)
@@ -2639,14 +2608,9 @@ def install(options, env, fstore, statestore):
except CalledProcessError, e:
root_logger.error(
"Failed to add %s to the default NSS database.", nickname)
- list_file.close()
return CLIENT_INSTALL_ERROR
- else:
- list_file.write(nickname + '\n')
root_logger.info("Added CA certificates to the default NSS database.")
- list_file.close()
-
if not options.on_master:
client_dns(cli_server[0], hostname, options.dns_updates)
diff --git a/ipa-client/ipaclient/ipa_certupdate.py b/ipa-client/ipaclient/ipa_certupdate.py
index f7b0e29..14bb25f 100644
--- a/ipa-client/ipaclient/ipa_certupdate.py
+++ b/ipa-client/ipaclient/ipa_certupdate.py
@@ -70,49 +70,32 @@ class CertUpdate(admintool.AdminTool):
def update_client(self, certs):
self.update_file(paths.IPA_CA_CRT, certs)
- self.update_db(paths.IPA_NSSDB_DIR, certs)
+ ipa_db = certdb.NSSDatabase(paths.IPA_NSSDB_DIR)
sys_db = certdb.NSSDatabase(paths.NSS_DB_DIR)
- for nickname in ('IPA CA', 'External CA cert'):
- try:
- sys_db.delete_cert(nickname)
- except ipautil.CalledProcessError, e:
- pass
- self.update_db(paths.NSS_DB_DIR, certs)
-
- new_nicknames = set(c[1] for c in certs)
- old_nicknames = set()
- if ipautil.file_exists(paths.NSSDB_IPA_TXT):
- try:
- list_file = open(paths.NSSDB_IPA_TXT, 'r')
- except IOError, e:
- self.log.error("failed to open %s: %s", paths.NSSDB_IPA_TXT, e)
- else:
+ # Remove IPA certs from /etc/pki/nssdb
+ for nickname, trust_flags in ipa_db.list_certs():
+ while sys_db.has_nickname(nickname):
try:
- lines = list_file.readlines()
- except IOError, e:
- self.log.error(
- "failed to read %s: %s", paths.NSSDB_IPA_TXT, e)
- else:
- for line in lines:
- nickname = line.strip()
- if nickname:
- old_nicknames.add(nickname)
- list_file.close()
- if new_nicknames != old_nicknames:
- try:
- list_file = open(paths.NSSDB_IPA_TXT, 'w')
- except IOError, e:
- self.log.error("failed to open %s: %s", paths.NSSDB_IPA_TXT, e)
- else:
+ sys_db.delete_cert(nickname)
+ except ipautil.CalledProcessError, e:
+ self.log.error("Failed to remove %s from %s: %s",
+ nickname, sys_db.secdir, e)
+ break
+
+ # Remove old IPA certs from /etc/ipa/nssdb
+ for nickname in ('IPA CA', 'External CA cert'):
+ while ipa_db.has_nickname(nickname):
try:
- for nickname in new_nicknames:
- list_file.write(nickname + '\n')
- except IOError, e:
- self.log.error(
- "failed to write %s: %s", paths.NSSDB_IPA_TXT, e)
- list_file.close()
+ ipa_db.delete_cert(nickname)
+ except ipautil.CalledProcessError, e:
+ self.log.error("Failed to remove %s from %s: %s",
+ nickname, ipa_db.secdir, e)
+ break
+
+ self.update_db(ipa_db.secdir, certs)
+ self.update_db(sys_db.secdir, certs)
tasks.remove_ca_certs_from_systemwide_ca_store()
tasks.insert_ca_certs_into_systemwide_ca_store(certs)
@@ -133,6 +116,7 @@ class CertUpdate(admintool.AdminTool):
criteria = {
'cert-database': dogtag_constants.ALIAS_DIR,
'cert-nickname': nickname,
+ 'ca-name': 'dogtag-ipa-ca-renew-agent',
}
request_id = certmonger.get_request_id(criteria)
if request_id is not None:
diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py
index 1493918..5f73d85 100644
--- a/ipaplatform/base/paths.py
+++ b/ipaplatform/base/paths.py
@@ -85,7 +85,6 @@ class BasePathNamespace(object):
NSSDB_CERT8_DB = "/etc/pki/nssdb/cert8.db"
NSSDB_KEY3_DB = "/etc/pki/nssdb/key3.db"
NSSDB_SECMOD_DB = "/etc/pki/nssdb/secmod.db"
- NSSDB_IPA_TXT = "/etc/pki/nssdb/ipa.txt"
PKI_TOMCAT = "/etc/pki/pki-tomcat"
PKI_TOMCAT_ALIAS_DIR = "/etc/pki/pki-tomcat/alias/"
PKI_TOMCAT_PASSWORD_CONF = "/etc/pki/pki-tomcat/password.conf"
--
1.9.3
>From 840b849535bbc74489c54cf1ea709bf8a9bfb7a0 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 18 Sep 2014 10:52:56 +0200
Subject: [PATCH 06/13] Check if IPA client is configured in ipa-certupdate
https://fedorahosted.org/freeipa/ticket/4460
---
ipa-client/ipaclient/ipa_certupdate.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/ipa-client/ipaclient/ipa_certupdate.py b/ipa-client/ipaclient/ipa_certupdate.py
index 14bb25f..48f11b4 100644
--- a/ipa-client/ipaclient/ipa_certupdate.py
+++ b/ipa-client/ipaclient/ipa_certupdate.py
@@ -41,6 +41,12 @@ class CertUpdate(admintool.AdminTool):
super(CertUpdate, self).validate_options(needs_root=True)
def run(self):
+ fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
+ if (not fstore.has_files() and
+ not os.path.exists(paths.IPA_DEFAULT_CONF)):
+ raise admintool.ScriptError(
+ "IPA client is not configured on this system.")
+
api.bootstrap(context='cli_installer')
api.finalize()
--
1.9.3
>From cfd3504f7ac4ed06985b4fd5d437cda0325c7e31 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 18 Sep 2014 11:15:49 +0200
Subject: [PATCH 07/13] Get server hostname from jsonrpc_uri in ipa-certupdate
---
ipa-client/ipaclient/ipa_certupdate.py | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/ipa-client/ipaclient/ipa_certupdate.py b/ipa-client/ipaclient/ipa_certupdate.py
index 48f11b4..ff16b9b 100644
--- a/ipa-client/ipaclient/ipa_certupdate.py
+++ b/ipa-client/ipaclient/ipa_certupdate.py
@@ -20,6 +20,7 @@
import os
import tempfile
import shutil
+import urlparse
from ipapython import (admintool, ipautil, ipaldap, sysrestore, dogtag,
certmonger, certdb)
@@ -50,10 +51,7 @@ class CertUpdate(admintool.AdminTool):
api.bootstrap(context='cli_installer')
api.finalize()
- try:
- server = api.env.server
- except AttributeError:
- server = api.env.host
+ server = urlparse.urlsplit(api.env.jsonrpc_uri).hostname
ldap = ipaldap.IPAdmin(server)
tmpdir = tempfile.mkdtemp(prefix="tmp-")
--
1.9.3
>From e93e4ecb8e64780675f375e33896b8e9de2db82d Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 17 Sep 2014 15:04:11 +0200
Subject: [PATCH 08/13] Remove ipa-ca.crt from systemwide CA store on client
uninstall and cert update
The file has been obsoleted by ipa.p11-kit, leaving it in place may cause
failures in ipa-server-install.
---
ipaplatform/base/paths.py | 1 +
ipaplatform/fedora/tasks.py | 38 ++++++++++++++++++++++++++++----------
2 files changed, 29 insertions(+), 10 deletions(-)
diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py
index 5f73d85..baaa109 100644
--- a/ipaplatform/base/paths.py
+++ b/ipaplatform/base/paths.py
@@ -80,6 +80,7 @@ class BasePathNamespace(object):
PAM_LDAP_CONF = "/etc/pam_ldap.conf"
PASSWD = "/etc/passwd"
ETC_PKI_CA_DIR = "/etc/pki-ca"
+ SYSTEMWIDE_CA_STORE = "/etc/pki/ca-trust/source/anchors/"
IPA_P11_KIT = "/etc/pki/ca-trust/source/ipa.p11-kit"
NSS_DB_DIR = "/etc/pki/nssdb"
NSSDB_CERT8_DB = "/etc/pki/nssdb/cert8.db"
diff --git a/ipaplatform/fedora/tasks.py b/ipaplatform/fedora/tasks.py
index 926c0ea..9de6360 100644
--- a/ipaplatform/fedora/tasks.py
+++ b/ipaplatform/fedora/tasks.py
@@ -155,6 +155,16 @@ class FedoraTaskNamespace(BaseTaskNamespace):
auth_config.execute()
def insert_ca_certs_into_systemwide_ca_store(self, ca_certs):
+ new_cacert_path = os.path.join(paths.SYSTEMWIDE_CA_STORE, 'ipa-ca.crt')
+
+ if os.path.exists(new_cacert_path):
+ try:
+ os.remove(new_cacert_path)
+ except OSError, e:
+ root_logger.error(
+ "Could not remove %s: %s", new_cacert_path, e)
+ return False
+
new_cacert_path = paths.IPA_P11_KIT
try:
@@ -247,25 +257,33 @@ class FedoraTaskNamespace(BaseTaskNamespace):
return False
def remove_ca_certs_from_systemwide_ca_store(self):
- new_cacert_path = paths.IPA_P11_KIT
+ ipa_ca_crt = os.path.join(paths.SYSTEMWIDE_CA_STORE, 'ipa-ca.crt')
+ update = False
# Remove CA cert from systemwide store
- if os.path.exists(new_cacert_path):
+ for new_cacert_path in (paths.IPA_P11_KIT, ipa_ca_crt):
+ if not os.path.exists(new_cacert_path):
+ continue
try:
os.remove(new_cacert_path)
- ipautil.run([paths.UPDATE_CA_TRUST])
except OSError, e:
- root_logger.error('Could not remove: %s, %s'
- % (new_cacert_path, str(e)))
- return False
+ root_logger.error(
+ "Could not remove %s: %s", new_cacert_path, e)
+ else:
+ update = True
+
+ if update:
+ try:
+ ipautil.run([paths.UPDATE_CA_TRUST])
except CalledProcessError, e:
- root_logger.error('Could not update systemwide CA trust '
- 'database: %s' % str(e))
+ root_logger.error(
+ "Could not update systemwide CA trust database: %s", e)
return False
else:
- root_logger.info('Systemwide CA database updated.')
+ root_logger.info("Systemwide CA database updated.")
+ return True
- return True
+ return False
def backup_and_replace_hostname(self, fstore, statestore, hostname):
old_hostname = socket.gethostname()
--
1.9.3
>From 4a5ebdbfdc2729da5e3c21b15617ec69955f6492 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 16 Sep 2014 10:34:57 +0200
Subject: [PATCH 09/13] Fix certmonger.wait_for_request
---
ipapython/certmonger.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/ipapython/certmonger.py b/ipapython/certmonger.py
index f58b6da..6d2b31f 100644
--- a/ipapython/certmonger.py
+++ b/ipapython/certmonger.py
@@ -468,7 +468,8 @@ def check_state(dirs):
def wait_for_request(request_id, timeout=120):
for i in range(0, timeout, 5):
- state = get_request_value(request_id, 'state').strip()
+ request = _get_request({'nickname': request_id})
+ state, blocked = request.obj_if.get_status()
root_logger.debug("certmonger request is in state %r", state)
if state in ('CA_REJECTED', 'CA_UNREACHABLE', 'CA_UNCONFIGURED',
'NEED_GUIDANCE', 'NEED_CA', 'MONITORING'):
--
1.9.3
>From 63abb6479c1f89b5996f19f69b3ef6cb1d8aba59 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 16 Sep 2014 10:38:31 +0200
Subject: [PATCH 10/13] Refactor dogtag-ipa-ca-renew-agent
---
.../certmonger/dogtag-ipa-ca-renew-agent-submit | 454 +++++++++++++--------
1 file changed, 287 insertions(+), 167 deletions(-)
diff --git a/install/certmonger/dogtag-ipa-ca-renew-agent-submit b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
index 4f0b78a..81aaf0a 100755
--- a/install/certmonger/dogtag-ipa-ca-renew-agent-submit
+++ b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
@@ -31,6 +31,7 @@ import tempfile
import shutil
import base64
import contextlib
+import json
from ipapython import ipautil
from ipapython.dn import DN
@@ -53,6 +54,119 @@ UNCONFIGURED = 4
WAIT_WITH_DELAY = 5
OPERATION_NOT_SUPPORTED_BY_HELPER = 6
+
+class RequestException(Exception):
+ code = None
+ body = ''
+
+
+class RequestGenericException(RequestException):
+ def __init__(self, code, body):
+ super(RequestGenericException, self).__init__(
+ "Unknown error {0}".format(code))
+ self.code = code
+ self.body = body
+
+
+class RequestWaitException(RequestException):
+ def __init__(self, cookie, message):
+ super(RequestWaitException, self).__init__(message)
+ self.cookie = cookie
+
+ @property
+ def body(self):
+ return '{0}\n'.format(self.cookie)
+
+
+class Error(RequestException):
+ @property
+ def body(self):
+ return '{0}\n'.format(self)
+
+
+class Wait(RequestWaitException):
+ def __init__(self, cookie=None):
+ super(Wait, self).__init__(
+ cookie, "Waiting for the request to complete")
+
+ code = WAIT
+
+
+class Rejected(Error):
+ code = REJECTED
+
+
+class Unreachable(Error):
+ code = UNREACHABLE
+
+
+class Unconfigured(Error):
+ code = UNCONFIGURED
+
+
+class WaitWithDelay(RequestWaitException):
+ code = WAIT_WITH_DELAY
+
+ def __init__(self, delay, cookie=None):
+ super(WaitWithDelay, self).__init__(
+ cookie, "Waiting {0}s for the request to complete".format(delay))
+ self.delay = delay
+
+ @property
+ def body(self):
+ return '{0}\n{1}'.format(
+ self.delay, super(WaitWithDelay, self).body)
+
+
+class OperationNotSupported(Error):
+ code = OPERATION_NOT_SUPPORTED_BY_HELPER
+
+
+def request_handler(fn):
+ def decorated(operation, csr, cookie=None, profile=None, cert=None):
+ if operation == 'POLL':
+ if not cookie:
+ raise Unconfigured("Cookie not provided")
+
+ try:
+ context = json.loads(cookie)
+ if not isinstance(context, dict):
+ raise TypeError
+ except (TypeError, ValueError):
+ raise Unconfigured("Invalid cookie: {0!r}".format(cookie))
+
+ try:
+ child_cookie = context.get('cookie')
+ if isinstance(child_cookie, unicode):
+ child_cookie = child_cookie.encode('raw_unicode_escape')
+ elif child_cookie is not None:
+ raise TypeError
+ except (TypeError, UnicodeEncodeError):
+ raise Unconfigured(
+ "Invalid child cookie in cookie: {0!r}".format(
+ child_cookie))
+ else:
+ context = {}
+ child_cookie = None
+
+ if profile is not None:
+ context['profile'] = profile.decode('raw_unicode_escape')
+ else:
+ context['profile'] = None
+
+ try:
+ return fn(context, operation, csr, child_cookie, profile, cert)
+ except RequestWaitException, e:
+ if e.cookie is not None:
+ context['cookie'] = e.cookie.decode('raw_unicode_escape')
+ else:
+ context.pop('cookie', None)
+ e.cookie = json.dumps(context)
+ raise e
+
+ return decorated
+
+
@contextlib.contextmanager
def ldap_connect():
conn = None
@@ -64,10 +178,28 @@ def ldap_connect():
if conn is not None and conn.isconnected():
conn.disconnect()
-def request_cert():
+
+@request_handler
+def request_cert(context, operation, csr, cookie=None, profile=None,
+ cert=None):
"""
Request certificate from IPA CA.
"""
+ os.environ['CERTMONGER_OPERATION'] = operation
+ os.environ['CERTMONGER_CSR'] = csr
+ if cookie is not None:
+ os.environ['CERTMONGER_CA_COOKIE'] = cookie
+ else:
+ os.environ.pop('CERTMONGER_CA_COOKIE', None)
+ if profile is not None:
+ os.environ['CERTMONGER_CA_PROFILE'] = profile
+ else:
+ os.environ.pop('CERTMONGER_CA_PROFILE', None)
+ if cert is not None:
+ os.environ['CERTMONGER_CERTIFICATE'] = cert
+ else:
+ os.environ.pop('CERTMONGER_CERTIFICATE', None)
+
syslog.syslog(syslog.LOG_NOTICE,
"Forwarding request to dogtag-ipa-renew-agent")
@@ -79,45 +211,51 @@ def request_cert():
syslog.syslog(syslog.LOG_NOTICE, "dogtag-ipa-renew-agent returned %d" % rc)
- if stdout.endswith('\n'):
- stdout = stdout[:-1]
-
- if rc == WAIT_WITH_DELAY:
- delay, sep, cookie = stdout.partition('\n')
- return (rc, delay, cookie)
+ body = stdout
+ if body.endswith('\n'):
+ body = body[:-1]
+
+ if rc == ISSUED:
+ return body
+ elif rc == WAIT:
+ raise Wait(body)
+ elif rc == REJECTED:
+ raise Rejected(body)
+ elif rc == UNREACHABLE:
+ raise Unreachable(body)
+ elif rc == UNCONFIGURED:
+ raise Unconfigured(body)
+ elif rc == WAIT_WITH_DELAY:
+ delay, cookie = body.split('\n', 1)
+ raise WaitWithDelay(delay, cookie)
+ elif rc == OPERATION_NOT_SUPPORTED_BY_HELPER:
+ raise OperationNotSupported()
else:
- return (rc, stdout)
+ raise RequestGenericException(rc, stdout)
+
-def store_cert():
+@request_handler
+def store_cert(context, operation, csr, cookie=None, profile=None, cert=None):
"""
Store certificate in LDAP.
"""
- operation = os.environ.get('CERTMONGER_OPERATION')
if operation == 'SUBMIT':
- attempts = 0
+ context['counter'] = 0
elif operation == 'POLL':
- cookie = os.environ.get('CERTMONGER_CA_COOKIE')
- if not cookie:
- return (UNCONFIGURED, "Cookie not provided")
-
- try:
- attempts = int(cookie)
- except ValueError:
- return (UNCONFIGURED, "Invalid cookie: %r" % cookie)
+ if 'counter' not in context:
+ raise Unconfigured("No counter in cookie")
+ if not isinstance(context['counter'], (int, long)):
+ raise Unconfigured(
+ "Invalid counter in cookie: {0!r}".format(context['counter']))
else:
- return (OPERATION_NOT_SUPPORTED_BY_HELPER,)
-
- csr = os.environ.get('CERTMONGER_CSR')
- if not csr:
- return (UNCONFIGURED, "Certificate request not provided")
+ raise OperationNotSupported()
nickname = pkcs10.get_friendlyname(csr)
if not nickname:
- return (REJECTED, "No friendly name in the certificate request")
+ raise Rejected("No friendly name in the certificate request")
- cert = os.environ.get('CERTMONGER_CERTIFICATE')
if not cert:
- return (REJECTED, "New certificate requests not supported")
+ raise Rejected("New certificate requests not supported")
dercert = x509.normalize_certificate(cert)
@@ -139,104 +277,88 @@ def store_cert():
except errors.EmptyModlist:
pass
except Exception, e:
- attempts += 1
- if attempts < 10:
+ context['counter'] += 1
+ if context['counter'] < 10:
syslog.syslog(
syslog.LOG_ERR,
"Updating renewal certificate failed: %s. Sleeping 30s" % e)
- return (WAIT_WITH_DELAY, 30, attempts)
+ raise WaitWithDelay(30)
else:
syslog.syslog(
syslog.LOG_ERR,
"Giving up. To retry storing the certificate, resubmit the "
"request with profile \"ipaStorage\"")
- return (ISSUED, cert)
+ return cert
+
-def request_and_store_cert():
+@request_handler
+def request_and_store_cert(context, operation, csr, cookie=None, profile=None,
+ cert=None):
"""
Request certificate from IPA CA and store it in LDAP.
"""
- operation = os.environ.get('CERTMONGER_OPERATION')
if operation == 'SUBMIT':
- state = 'request'
- cookie = None
+ context['state'] = u'request'
elif operation == 'POLL':
- cookie = os.environ.get('CERTMONGER_CA_COOKIE')
- if not cookie:
- return (UNCONFIGURED, "Cookie not provided")
-
- state, sep, cookie = cookie.partition(':')
- if state not in ('request', 'store'):
- return (UNCONFIGURED,
- "Invalid cookie: %r" % os.environ['CERTMONGER_CA_COOKIE'])
- else:
- return (OPERATION_NOT_SUPPORTED_BY_HELPER,)
+ if 'state' not in context:
+ raise Unconfigured("No state name in cookie")
+ if context['state'] not in (u'request', u'store'):
+ raise Unconfigured(
+ "Invalid state name in cookie: {0!r}".format(context['state']))
- if state == 'request':
- if cookie is None:
- os.environ['CERTMONGER_OPERATION'] = 'SUBMIT'
- else:
- os.environ['CERTMONGER_CA_COOKIE'] = cookie
-
- result = request_cert()
- if result[0] == WAIT:
- return (result[0], 'request:%s' % result[1])
- elif result[0] == WAIT_WITH_DELAY:
- return (result[0], result[1], 'request:%s' % result[2])
- elif result[0] != ISSUED:
- return result
- else:
- cert = result[1]
- cookie = None
+ if context['state'] == u'store':
+ try:
+ cert = context['certificate']
+ if isinstance(cert, unicode):
+ cert = cert.encode('raw_unicode_escape')
+ else:
+ raise TypeError
+ except KeyError:
+ raise Unconfigured("No certificate in cookie")
+ except (TypeError, UnicodeEncodeError):
+ raise Unconfigured(
+ "Invalid certificate in cookie: {0!r}".format(cert))
else:
- cert, sep, cookie = cookie.partition(':')
+ raise OperationNotSupported()
- if cookie is None:
- os.environ['CERTMONGER_OPERATION'] = 'SUBMIT'
- else:
- os.environ['CERTMONGER_CA_COOKIE'] = cookie
- os.environ['CERTMONGER_CERTIFICATE'] = cert
+ if context['state'] == u'request':
+ cert = request_cert(operation, csr, cookie, profile, cert)
+ context['state'] = u'store'
+ context['certificate'] = cert.decode('raw_unicode_escape')
+ operation = 'SUBMIT'
+ cookie = None
- result = store_cert()
- if result[0] == WAIT:
- return (result[0], 'store:%s:%s' % (cert, result[1]))
- elif result[0] == WAIT_WITH_DELAY:
- return (result[0], result[1], 'store:%s:%s' % (cert, result[2]))
- else:
- return result
+ if context['state'] == u'store':
+ cert = store_cert(operation, csr, cookie, profile, cert)
-def retrieve_cert():
+ return cert
+
+
+@request_handler
+def retrieve_cert(context, operation, csr, cookie=None, profile=None,
+ cert=None):
"""
Retrieve new certificate from LDAP.
"""
- operation = os.environ.get('CERTMONGER_OPERATION')
if operation == 'SUBMIT':
- attempts = 0
+ context['counter'] = 0
elif operation == 'POLL':
- cookie = os.environ.get('CERTMONGER_CA_COOKIE')
- if not cookie:
- return (UNCONFIGURED, "Cookie not provided")
-
- try:
- attempts = int(cookie)
- except ValueError:
- return (UNCONFIGURED, "Invalid cookie: %r" % cookie)
+ if 'counter' not in context:
+ raise Unconfigured("No counter in cookie")
+ if not isinstance(context['counter'], (int, long)):
+ raise Unconfigured(
+ "Invalid counter in cookie: {0!r}".format(context['counter']))
else:
- return (OPERATION_NOT_SUPPORTED_BY_HELPER,)
-
- csr = os.environ.get('CERTMONGER_CSR')
- if not csr:
- return (UNCONFIGURED, "Certificate request not provided")
+ raise OperationNotSupported()
nickname = pkcs10.get_friendlyname(csr)
if not nickname:
- return (REJECTED, "No friendly name in the certificate request")
+ raise Rejected("No friendly name in the certificate request")
- old_cert = os.environ.get('CERTMONGER_CERTIFICATE')
- if not old_cert:
- return (REJECTED, "New certificate requests not supported")
- old_cert = x509.normalize_certificate(old_cert)
+ if not cert:
+ raise Rejected("New certificate requests not supported")
+ old_cert = x509.normalize_certificate(cert)
syslog.syslog(syslog.LOG_NOTICE, "Updating certificate for %s" % nickname)
@@ -252,132 +374,130 @@ def retrieve_cert():
cert = entry.single_value['usercertificate']
if cert == old_cert:
- attempts += 1
- if attempts < 4:
+ context['counter'] += 1
+ if context['counter'] < 4:
syslog.syslog(
syslog.LOG_INFO,
"Updated certificate for %s not available" % nickname)
- # No cert available yet, tell certmonger to wait another 8 hours
- return (WAIT_WITH_DELAY, 8 * 60 * 60, attempts)
+ # No cert available yet, tell certmonger to wait another 8 hrs
+ raise WaitWithDelay(8 * 60 * 60)
cert = base64.b64encode(cert)
cert = x509.make_pem(cert)
- return (ISSUED, cert)
+ return cert
+
-def export_csr():
+@request_handler
+def export_csr(context, operation, csr, cookie=None, profile=None, cert=None):
"""
This does not actually renew the cert, it just writes the CSR provided
by certmonger to /var/lib/ipa/ca.csr and returns the existing cert.
"""
- operation = os.environ.get('CERTMONGER_OPERATION')
if operation != 'SUBMIT':
- return (OPERATION_NOT_SUPPORTED_BY_HELPER,)
+ raise OperationNotSupported()
- csr = os.environ.get('CERTMONGER_CSR')
- if not csr:
- return (UNCONFIGURED, "Certificate request not provided")
-
- cert = os.environ.get('CERTMONGER_CERTIFICATE')
if not cert:
- return (REJECTED, "New certificate requests not supported")
+ raise Rejected("New certificate requests not supported")
csr_file = paths.IPA_CA_CSR
try:
with open(csr_file, 'wb') as f:
f.write(csr)
except Exception, e:
- return (UNREACHABLE, "Failed to write %s: %s" % (csr_file, e))
+ raise Unreachable("Failed to write {0}: {1}".format(csr_file, e))
- return (ISSUED, cert)
+ return cert
-def renew_ca_cert():
+
+@request_handler
+def renew_ca_cert(context, operation, csr, cookie=None, profile=None,
+ cert=None):
"""
This is used for automatic CA certificate renewal.
"""
cert = os.environ.get('CERTMONGER_CERTIFICATE')
if not cert:
- return (REJECTED, "New certificate requests not supported")
+ raise Rejected("New certificate requests not supported")
is_self_signed = x509.is_self_signed(cert)
- operation = os.environ.get('CERTMONGER_OPERATION')
if operation == 'SUBMIT':
- state = 'retrieve'
-
+ context['state'] = u'retrieve'
if is_self_signed:
ca = cainstance.CAInstance(host_name=api.env.host, ldapi=False)
if ca.is_renewal_master():
- state = 'request'
+ context['state'] = u'request'
elif operation == 'POLL':
- cookie = os.environ.get('CERTMONGER_CA_COOKIE')
- if not cookie:
- return (UNCONFIGURED, "Cookie not provided")
+ if 'state' not in context:
+ raise Unconfigured("No state name in cookie")
+ if context['state'] not in (u'retrieve', u'request'):
+ raise Unconfigured(
+ "Invalid state name in cookie: {0!r}".format(context['state']))
+ else:
+ raise OperationNotSupported()
- state, sep, cookie = cookie.partition(':')
- if state not in ('retrieve', 'request'):
- return (UNCONFIGURED,
- "Invalid cookie: %r" % os.environ['CERTMONGER_CA_COOKIE'])
+ if context['state'] == u'retrieve':
+ try:
+ cert = retrieve_cert(operation, csr, cookie, profile, cert)
+ except WaitWithDelay:
+ if not is_self_signed:
+ syslog.syslog(syslog.LOG_ALERT,
+ "IPA CA certificate is about to expire, "
+ "use ipa-cacert-manage to renew it")
+ raise
+ elif context['state'] == u'request':
+ profile = 'caCACert'
+ cert = request_and_store_cert(operation, csr, cookie, profile, cert)
+
+ return cert
- os.environ['CERTMONGER_CA_COOKIE'] = cookie
- else:
- return (OPERATION_NOT_SUPPORTED_BY_HELPER,)
-
- if state == 'retrieve':
- result = retrieve_cert()
- if result[0] == WAIT_WITH_DELAY and not is_self_signed:
- syslog.syslog(syslog.LOG_ALERT,
- "IPA CA certificate is about to expire, "
- "use ipa-cacert-manage to renew it")
- elif state == 'request':
- os.environ['CERTMONGER_CA_PROFILE'] = 'caCACert'
- result = request_and_store_cert()
-
- if result[0] == WAIT:
- return (result[0], '%s:%s' % (state, result[1]))
- elif result[0] == WAIT_WITH_DELAY:
- return (result[0], result[1], '%s:%s' % (state, result[2]))
- else:
- return result
def main():
- handlers = {
- 'ipaStorage': store_cert,
- 'ipaRetrieval': retrieve_cert,
- 'ipaCSRExport': export_csr,
- 'ipaCACertRenewal': renew_ca_cert,
- }
+ operation = os.environ.get('CERTMONGER_OPERATION') or None
+ if operation not in ('SUBMIT', 'POLL'):
+ raise OperationNotSupported()
+
+ csr = os.environ.get('CERTMONGER_CSR') or None
+ if not csr:
+ raise Unconfigured("Certificate request not provided")
+
+ cookie = os.environ.get('CERTMONGER_CA_COOKIE') or None
+ profile = os.environ.get('CERTMONGER_CA_PROFILE') or None
+ cert = os.environ.get('CERTMONGER_CERTIFICATE') or None
api.bootstrap(context='renew')
api.finalize()
- operation = os.environ.get('CERTMONGER_OPERATION')
- if operation not in ('SUBMIT', 'POLL'):
- return OPERATION_NOT_SUPPORTED_BY_HELPER
-
tmpdir = tempfile.mkdtemp(prefix="tmp-")
try:
principal = str('host/%s@%s' % (api.env.host, api.env.realm))
ipautil.kinit_hostprincipal(paths.KRB5_KEYTAB, tmpdir, principal)
- profile = os.environ.get('CERTMONGER_CA_PROFILE')
- if profile:
- handler = handlers.get(profile, request_and_store_cert)
- else:
+ if not profile:
ca = cainstance.CAInstance(host_name=api.env.host, ldapi=False)
- if ca.is_renewal_master():
- handler = request_and_store_cert
- else:
- handler = retrieve_cert
+ if not ca.is_renewal_master():
+ profile = 'ipaRetrieval'
- res = handler()
- for item in res[1:]:
- print item
- return res[0]
+ handler = {
+ 'ipaStorage': store_cert,
+ 'ipaRetrieval': retrieve_cert,
+ 'ipaCSRExport': export_csr,
+ 'ipaCACertRenewal': renew_ca_cert,
+ }.get(profile, request_and_store_cert)
+
+ cert = handler(operation, csr, cookie, profile, cert)
finally:
shutil.rmtree(tmpdir)
+ print cert
+
try:
- sys.exit(main())
+ main()
+ sys.exit(ISSUED)
+except RequestException, e:
+ syslog.syslog(syslog.LOG_INFO, str(e))
+ sys.stdout.write(e.body)
+ sys.exit(e.code)
except Exception, e:
syslog.syslog(syslog.LOG_ERR, traceback.format_exc())
print "Internal error"
--
1.9.3
>From b4c64fd7f8aa36d04dc2d8a78da54807a4a8fd8e Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 16 Sep 2014 10:38:48 +0200
Subject: [PATCH 11/13] Restart request in dogtag-ipa-ca-renew-agent if profile
changes
---
install/certmonger/dogtag-ipa-ca-renew-agent-submit | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/install/certmonger/dogtag-ipa-ca-renew-agent-submit b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
index 81aaf0a..8690fb8 100755
--- a/install/certmonger/dogtag-ipa-ca-renew-agent-submit
+++ b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
@@ -136,6 +136,20 @@ def request_handler(fn):
raise Unconfigured("Invalid cookie: {0!r}".format(cookie))
try:
+ context_profile = context['profile']
+ if isinstance(context_profile, unicode):
+ context_profile = context_profile.encode(
+ 'raw_unicode_escape')
+ elif context_profile is not None:
+ raise TypeError
+ except KeyError:
+ raise Unconfigured("No profile name in cookie")
+ except (TypeError, UnicodeEncodeError):
+ raise Unconfigured(
+ "Invalid profile name {0!r} in cookie".format(
+ context_profile))
+
+ try:
child_cookie = context.get('cookie')
if isinstance(child_cookie, unicode):
child_cookie = child_cookie.encode('raw_unicode_escape')
@@ -145,6 +159,12 @@ def request_handler(fn):
raise Unconfigured(
"Invalid child cookie in cookie: {0!r}".format(
child_cookie))
+
+ # If profile has changed between SUBMIT and POLL, restart request
+ if context_profile != profile:
+ operation = 'SUBMIT'
+ context = {}
+ child_cookie = None
else:
context = {}
child_cookie = None
--
1.9.3
>From c2f5b6194d8708a16bb7de5cbaf45bef35a345b3 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 18 Sep 2014 10:28:03 +0200
Subject: [PATCH 12/13] Do not wait for new CA certificate to appear in LDAP in
ipa-certupdate
---
.../certmonger/dogtag-ipa-ca-renew-agent-submit | 66 ++++++++++++++--------
ipa-client/ipaclient/ipa_certupdate.py | 2 +-
2 files changed, 43 insertions(+), 25 deletions(-)
diff --git a/install/certmonger/dogtag-ipa-ca-renew-agent-submit b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
index 8690fb8..f6a695b 100755
--- a/install/certmonger/dogtag-ipa-ca-renew-agent-submit
+++ b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
@@ -356,20 +356,13 @@ def request_and_store_cert(context, operation, csr, cookie=None, profile=None,
@request_handler
-def retrieve_cert(context, operation, csr, cookie=None, profile=None,
- cert=None):
+def try_retrieve_cert(context, operation, csr, cookie=None, profile=None,
+ cert=None):
"""
- Retrieve new certificate from LDAP.
+ Retrieve new certificate from LDAP. If new certificate is not available,
+ use the old one.
"""
- if operation == 'SUBMIT':
- context['counter'] = 0
- elif operation == 'POLL':
- if 'counter' not in context:
- raise Unconfigured("No counter in cookie")
- if not isinstance(context['counter'], (int, long)):
- raise Unconfigured(
- "Invalid counter in cookie: {0!r}".format(context['counter']))
- else:
+ if operation != 'SUBMIT':
raise OperationNotSupported()
nickname = pkcs10.get_friendlyname(csr)
@@ -378,7 +371,6 @@ def retrieve_cert(context, operation, csr, cookie=None, profile=None,
if not cert:
raise Rejected("New certificate requests not supported")
- old_cert = x509.normalize_certificate(cert)
syslog.syslog(syslog.LOG_NOTICE, "Updating certificate for %s" % nickname)
@@ -389,21 +381,46 @@ def retrieve_cert(context, operation, csr, cookie=None, profile=None,
('cn', 'ipa'), ('cn', 'etc'), api.env.basedn),
['usercertificate'])
except errors.NotFound:
- cert = old_cert
+ pass
else:
cert = entry.single_value['usercertificate']
+ cert = base64.b64encode(cert)
+ cert = x509.make_pem(cert)
+
+ return cert
+
+
+@request_handler
+def retrieve_cert(context, operation, csr, cookie=None, profile=None,
+ cert=None):
+ """
+ Retrieve new certificate from LDAP.
+ """
+ if operation == 'SUBMIT':
+ context['counter'] = 0
+ elif operation == 'POLL':
+ if 'counter' not in context:
+ raise Unconfigured("No counter in cookie")
+ if not isinstance(context['counter'], (int, long)):
+ raise Unconfigured(
+ "Invalid counter in cookie: {0!r}".format(context['counter']))
+ else:
+ raise OperationNotSupported()
+
+ if cert:
+ old_cert = x509.normalize_certificate(cert)
+ else:
+ old_cert = None
- if cert == old_cert:
- context['counter'] += 1
- if context['counter'] < 4:
- syslog.syslog(
- syslog.LOG_INFO,
- "Updated certificate for %s not available" % nickname)
- # No cert available yet, tell certmonger to wait another 8 hrs
- raise WaitWithDelay(8 * 60 * 60)
+ cert = try_retrieve_cert('SUBMIT', csr, cookie, profile, cert)
+ new_cert = x509.normalize_certificate(cert)
- cert = base64.b64encode(cert)
- cert = x509.make_pem(cert)
+ if new_cert == old_cert:
+ context['counter'] += 1
+ if context['counter'] < 4:
+ syslog.syslog(syslog.LOG_INFO, "Updated certificate not available")
+ # No cert available yet, tell certmonger to wait another 8 hours
+ raise WaitWithDelay(8 * 60 * 60)
return cert
@@ -500,6 +517,7 @@ def main():
handler = {
'ipaStorage': store_cert,
+ 'ipaTryRetrieval': try_retrieve_cert,
'ipaRetrieval': retrieve_cert,
'ipaCSRExport': export_csr,
'ipaCACertRenewal': renew_ca_cert,
diff --git a/ipa-client/ipaclient/ipa_certupdate.py b/ipa-client/ipaclient/ipa_certupdate.py
index ff16b9b..cdf9d48 100644
--- a/ipa-client/ipaclient/ipa_certupdate.py
+++ b/ipa-client/ipaclient/ipa_certupdate.py
@@ -127,7 +127,7 @@ class CertUpdate(admintool.AdminTool):
timeout = api.env.startup_timeout + 60
self.log.debug("resubmitting certmonger request '%s'", request_id)
- certmonger.resubmit_request(request_id, profile='ipaRetrieval')
+ certmonger.resubmit_request(request_id, profile='ipaTryRetrieval')
try:
state = certmonger.wait_for_request(request_id, timeout)
except RuntimeError:
--
1.9.3
>From e2a83247b4bc5fe6cec37b1f33d764a6641c2461 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 18 Sep 2014 10:29:26 +0200
Subject: [PATCH 13/13] Fail if certmonger can't see new CA certificate in LDAP
in ipa-cacert-manage
---
.../certmonger/dogtag-ipa-ca-renew-agent-submit | 25 ++++++++++++++++++++++
ipaserver/install/ipa_cacert_manage.py | 9 +++++---
2 files changed, 31 insertions(+), 3 deletions(-)
diff --git a/install/certmonger/dogtag-ipa-ca-renew-agent-submit b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
index f6a695b..32c5cab 100755
--- a/install/certmonger/dogtag-ipa-ca-renew-agent-submit
+++ b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
@@ -391,6 +391,30 @@ def try_retrieve_cert(context, operation, csr, cookie=None, profile=None,
@request_handler
+def do_retrieve_cert(context, operation, csr, cookie=None, profile=None,
+ cert=None):
+ """
+ Retrieve new certificate from LDAP. If new certificate is not available,
+ fail.
+ """
+ if operation != 'SUBMIT':
+ raise OperationNotSupported()
+
+ if cert:
+ old_cert = x509.normalize_certificate(cert)
+ else:
+ old_cert = None
+
+ cert = try_retrieve_cert('SUBMIT', csr, cookie, profile, cert)
+ new_cert = x509.normalize_certificate(cert)
+
+ if new_cert == old_cert:
+ raise Rejected("Updated certificate not available")
+
+ return cert
+
+
+@request_handler
def retrieve_cert(context, operation, csr, cookie=None, profile=None,
cert=None):
"""
@@ -518,6 +542,7 @@ def main():
handler = {
'ipaStorage': store_cert,
'ipaTryRetrieval': try_retrieve_cert,
+ 'ipaDoRetrieval': do_retrieve_cert,
'ipaRetrieval': retrieve_cert,
'ipaCSRExport': export_csr,
'ipaCACertRenewal': renew_ca_cert,
diff --git a/ipaserver/install/ipa_cacert_manage.py b/ipaserver/install/ipa_cacert_manage.py
index c681261..67103a6e 100644
--- a/ipaserver/install/ipa_cacert_manage.py
+++ b/ipaserver/install/ipa_cacert_manage.py
@@ -153,8 +153,11 @@ class CACertManage(admintool.AdminTool):
raise admintool.ScriptError("CA is not configured on this system")
nss_dir = ca.dogtag_constants.ALIAS_DIR
- criteria = {'cert-database': nss_dir,
- 'cert-nickname': self.cert_nickname}
+ criteria = {
+ 'cert-database': nss_dir,
+ 'cert-nickname': self.cert_nickname,
+ 'ca-name': 'dogtag-ipa-ca-renew-agent',
+ }
self.request_id = certmonger.get_request_id(criteria)
if self.request_id is None:
raise admintool.ScriptError(
@@ -291,7 +294,7 @@ class CACertManage(admintool.AdminTool):
except errors.NotFound:
raise admintool.ScriptError("CA renewal master not found")
- self.resubmit_request(ca, 'ipaRetrieval')
+ self.resubmit_request(ca, 'ipaDoRetrieval')
print "CA certificate successfully renewed"
--
1.9.3
_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel