summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ipalib/plugins/cert.py29
-rw-r--r--ipaserver/install/Makefile.am1
-rw-r--r--ipaserver/install/certmonger.py152
-rw-r--r--ipaserver/install/certs.py51
-rw-r--r--ipaserver/install/dsinstance.py27
-rw-r--r--ipaserver/install/httpinstance.py11
-rw-r--r--ipaserver/install/service.py2
7 files changed, 254 insertions, 19 deletions
diff --git a/ipalib/plugins/cert.py b/ipalib/plugins/cert.py
index 1154e2e30..60161cf1c 100644
--- a/ipalib/plugins/cert.py
+++ b/ipalib/plugins/cert.py
@@ -417,7 +417,16 @@ class cert_show(VirtualCommand):
operation="retrieve certificate"
def execute(self, serial_number):
- self.check_access()
+ hostname = None
+ try:
+ self.check_access()
+ except errors.ACIError, acierr:
+ self.debug("Not granted by ACI to retrieve certificate, looking at principal")
+ bind_principal = getattr(context, 'principal')
+ if not bind_principal.startswith('host/'):
+ raise acierr
+ hostname = get_host_from_principal(bind_principal)
+
result=self.Backend.ra.get_certificate(serial_number)
cert = x509.load_certificate(result['certificate'])
result['subject'] = unicode(cert.subject)
@@ -426,6 +435,12 @@ class cert_show(VirtualCommand):
result['valid_not_after'] = unicode(cert.valid_not_after_str)
result['md5_fingerprint'] = unicode(nss.data_to_hex(nss.md5_digest(cert.der_data), 64)[0])
result['sha1_fingerprint'] = unicode(nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[0])
+ if hostname:
+ # If we have a hostname we want to verify that the subject
+ # of the certificate matches it, otherwise raise an error
+ if hostname != cert.subject.common_name:
+ raise acierr
+
return dict(result=result)
api.register(cert_show)
@@ -457,7 +472,17 @@ class cert_revoke(VirtualCommand):
)
def execute(self, serial_number, **kw):
- self.check_access()
+ hostname = None
+ try:
+ self.check_access()
+ except errors.ACIError, acierr:
+ self.debug("Not granted by ACI to revoke certificate, looking at principal")
+ try:
+ # Let cert_show() handle verifying that the subject of the
+ # cert we're dealing with matches the hostname in the principal
+ result = api.Command['cert_show'](unicode(serial_number))['result']
+ except errors.NotImplementedError:
+ pass
return dict(
result=self.Backend.ra.revoke_certificate(serial_number, **kw)
)
diff --git a/ipaserver/install/Makefile.am b/ipaserver/install/Makefile.am
index 964837cb9..8932eadbb 100644
--- a/ipaserver/install/Makefile.am
+++ b/ipaserver/install/Makefile.am
@@ -15,6 +15,7 @@ app_PYTHON = \
replication.py \
certs.py \
ldapupdate.py \
+ certmonger.py \
$(NULL)
EXTRA_DIST = \
diff --git a/ipaserver/install/certmonger.py b/ipaserver/install/certmonger.py
new file mode 100644
index 000000000..bb56c2ab3
--- /dev/null
+++ b/ipaserver/install/certmonger.py
@@ -0,0 +1,152 @@
+# Authors: Rob Crittenden <rcritten@redhat.com>
+#
+# Copyright (C) 2010 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+# Some certmonger functions, mostly around updating the request file.
+# This is used so we can add tracking to the Apache and 389-ds
+# server certificates created during the IPA server installation.
+
+import os
+import re
+import time
+from ipapython import ipautil
+
+REQUEST_DIR='/var/lib/certmonger/requests/'
+
+def find_request_value(filename, directive):
+ """
+ Return a value from a certmonger request file for the requested directive
+
+ It tries to do this a number of times because sometimes there is a delay
+ when ipa-getcert returns and the file is fully updated, particularly
+ when doing a request. Genrerating a CSR is fast but not instantaneous.
+ """
+ tries = 1
+ value = None
+ found = False
+ while value is None and tries <= 5:
+ tries=tries + 1
+ time.sleep(1)
+ fp = open(filename, 'r')
+ lines = fp.readlines()
+ fp.close()
+
+ for line in lines:
+ if found:
+ # A value can span multiple lines. If it does then it has a
+ # leading space.
+ if not line.startswith(' '):
+ # We hit the next directive, return now
+ return value
+ else:
+ value = value + line[1:]
+ else:
+ if line.startswith(directive + '='):
+ found = True
+ value = line[len(directive)+1:]
+
+ return value
+
+def get_request_value(request_id, directive):
+ """
+ There is no guarantee that the request_id will match the filename
+ in the certmonger requests directory, so open each one to find the
+ request_id.
+ """
+ fileList=os.listdir(REQUEST_DIR)
+ for file in fileList:
+ value = find_request_value('%s/%s' % (REQUEST_DIR, file), 'id')
+ if value is not None and value.rstrip() == request_id:
+ return find_request_value('%s/%s' % (REQUEST_DIR, file), directive)
+
+ return None
+
+def add_request_value(request_id, directive, value):
+ """
+ Add a new directive to a certmonger request file.
+
+ The certmonger service MUST be stopped in order for this to work.
+ """
+ fileList=os.listdir(REQUEST_DIR)
+ for file in fileList:
+ id = find_request_value('%s/%s' % (REQUEST_DIR, file), 'id')
+ if id is not None and id.rstrip() == request_id:
+ current_value = find_request_value('%s/%s' % (REQUEST_DIR, file), directive)
+ if not current_value:
+ fp = open('%s/%s' % (REQUEST_DIR, file), 'a')
+ fp.write('%s=%s\n' % (directive, value))
+ fp.close()
+
+ return
+
+def add_principal(request_id, principal):
+ """
+ In order for a certmonger request to be renwable it needs a principal.
+
+ When an existing certificate is added via start-tracking it won't have
+ a principal.
+ """
+ return add_request_value(request_id, 'template_principal', principal)
+
+def add_subject(request_id, subject):
+ """
+ In order for a certmonger request to be renwable it needs the subject
+ set in the request file.
+
+ When an existing certificate is added via start-tracking it won't have
+ a subject_template set.
+ """
+ return add_request_value(request_id, 'template_subject', subject)
+
+def request_cert(nssdb, nickname, subject, principal, passwd_fname=None):
+ """
+ Execute certmonger to request a server certificate
+ """
+ args = ['/usr/bin/ipa-getcert',
+ 'request',
+ '-d', nssdb,
+ '-n', nickname,
+ '-N', subject,
+ '-K', principal,
+ ]
+ if passwd_fname:
+ args.append('-p')
+ args.append(passwd_fname)
+ (stdout, stderr, returncode) = ipautil.run(args)
+ # FIXME: should be some error handling around this
+ m = re.match('New signing request "(\d+)" added', stdout)
+ request_id = m.group(1)
+ return request_id
+
+def stop_tracking(request_id):
+ """
+ Stop tracking the current request.
+
+ This assumes that the certmonger service is running.
+ """
+ args = ['/usr/bin/ipa-getcert',
+ 'stop-tracking',
+ '-i', request_id
+ ]
+ (stdout, stderr, returncode) = ipautil.run(args)
+
+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')
+ print csr
+ stop_tracking(request_id)
diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py
index cf89c22f0..7f246d11c 100644
--- a/ipaserver/install/certs.py
+++ b/ipaserver/install/certs.py
@@ -34,6 +34,9 @@ from ipapython import sysrestore
from ipapython import ipautil
from ipalib import pkcs10
from ConfigParser import RawConfigParser
+import service
+import certmonger
+from ipalib import x509
from nss.error import NSPRError
import nss.nss as nss
@@ -432,6 +435,51 @@ class CertDB(object):
raise RuntimeError("Unable to find serial number")
+ def track_server_cert(self, nickname, principal, password_file=None):
+ """
+ Tell certmonger to track the given certificate nickname.
+ """
+ service.chkconfig_on("certmonger")
+ service.start("certmonger")
+ args = ["/usr/bin/ipa-getcert", "start-tracking",
+ "-d", self.secdir,
+ "-n", nickname]
+ if password_file:
+ args.append("-p")
+ args.append(password_file)
+ try:
+ (stdout, stderr, returncode) = ipautil.run(args)
+ except ipautil.CalledProcessError, e:
+ logging.error("tracking certificate failed: %s" % str(e))
+
+ service.stop("certmonger")
+ cert = self.get_cert_from_db(nickname)
+ subject = str(x509.get_subject(cert))
+ m = re.match('New tracking request "(\d+)" added', stdout)
+ request_id = m.group(1)
+
+ certmonger.add_principal(request_id, principal)
+ certmonger.add_subject(request_id, subject)
+
+ service.start("certmonger")
+
+ def untrack_server_cert(self, nickname):
+ """
+ Tell certmonger to stop tracking the given certificate nickname.
+ """
+
+ # Always start certmonger. We can't untrack something if it isn't
+ # running
+ service.start("certmonger")
+ args = ["/usr/bin/ipa-getcert", "stop-tracking",
+ "-d", self.secdir,
+ "-n", nickname]
+ try:
+ (stdout, stderr, returncode) = ipautil.run(args)
+ except ipautil.CalledProcessError, e:
+ logging.error("untracking certificate failed: %s" % str(e))
+ service.stop("certmonger")
+
def create_server_cert(self, nickname, hostname, other_certdb=None, subject=None):
"""
other_certdb can mean one of two things, depending on the context.
@@ -449,7 +497,7 @@ class CertDB(object):
cdb = self
if subject is None:
subject=self.subject_format % hostname
- (out, err) = self.request_cert(subject)
+ self.request_cert(subject)
cdb.issue_server_cert(self.certreq_fname, self.certder_fname)
self.add_cert(self.certder_fname, nickname)
fd = open(self.certder_fname, "r")
@@ -486,7 +534,6 @@ class CertDB(object):
args.append("-a")
(stdout, stderr, returncode) = self.run_certutil(args)
os.remove(self.noise_fname)
-
return (stdout, stderr)
def issue_server_cert(self, certreq_fname, cert_fname):
diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
index f91d69b7b..a53348456 100644
--- a/ipaserver/install/dsinstance.py
+++ b/ipaserver/install/dsinstance.py
@@ -165,7 +165,7 @@ class DsInstance(service.Service):
self.sub_dict = None
self.domain = domain_name
self.serverid = None
- self.host_name = None
+ self.fqdn = None
self.pkcs12_info = None
self.ds_user = None
self.dercert = None
@@ -177,19 +177,19 @@ class DsInstance(service.Service):
else:
self.suffix = None
- def create_instance(self, ds_user, realm_name, host_name, domain_name, dm_password, pkcs12_info=None, self_signed_ca=False, uidstart=1100, gidstart=1100, subject_base=None, hbac_allow=True):
+ def create_instance(self, ds_user, realm_name, fqdn, domain_name, dm_password, pkcs12_info=None, self_signed_ca=False, uidstart=1100, gidstart=1100, subject_base=None, hbac_allow=True):
self.ds_user = ds_user
self.realm_name = realm_name.upper()
self.serverid = realm_to_serverid(self.realm_name)
self.suffix = util.realm_to_suffix(self.realm_name)
- self.host_name = host_name
+ self.fqdn = fqdn
self.dm_password = dm_password
self.domain = domain_name
self.pkcs12_info = pkcs12_info
self.self_signed_ca = self_signed_ca
self.uidstart = uidstart
self.gidstart = gidstart
- self.principal = "ldap/%s@%s" % (self.host_name, self.realm_name)
+ self.principal = "ldap/%s@%s" % (self.fqdn, self.realm_name)
self.subject_base = subject_base
self.__setup_sub_dict()
@@ -232,12 +232,12 @@ class DsInstance(service.Service):
def __setup_sub_dict(self):
server_root = find_server_root()
- self.sub_dict = dict(FQHN=self.host_name, SERVERID=self.serverid,
+ self.sub_dict = dict(FQHN=self.fqdn, SERVERID=self.serverid,
PASSWORD=self.dm_password, SUFFIX=self.suffix.lower(),
REALM=self.realm_name, USER=self.ds_user,
SERVER_ROOT=server_root, DOMAIN=self.domain,
TIME=int(time.time()), UIDSTART=self.uidstart,
- GIDSTART=self.gidstart, HOST=self.host_name,
+ GIDSTART=self.gidstart, HOST=self.fqdn,
ESCAPED_SUFFIX= escape_dn_chars(self.suffix.lower()),
)
@@ -356,7 +356,7 @@ class DsInstance(service.Service):
def __config_uidgid_gen_first_master(self):
if (self.uidstart == self.gidstart and
- has_managed_entries(self.host_name, self.dm_password)):
+ has_managed_entries(self.fqdn, self.dm_password)):
self._ldap_mod("dna-upg.ldif", self.sub_dict)
else:
self._ldap_mod("dna-posix.ldif", self.sub_dict)
@@ -377,7 +377,7 @@ class DsInstance(service.Service):
self._ldap_mod("version-conf.ldif")
def __user_private_groups(self):
- if has_managed_entries(self.host_name, self.dm_password):
+ if has_managed_entries(self.fqdn, self.dm_password):
self._ldap_mod("user_private_groups.ldif", self.sub_dict)
def __add_enrollment_module(self):
@@ -397,17 +397,19 @@ class DsInstance(service.Service):
self.dercert = dsdb.get_cert_from_db(nickname)
else:
nickname = "Server-Cert"
- cadb = certs.CertDB(httpinstance.NSS_DIR, host_name=self.host_name, subject_base=self.subject_base)
+ cadb = certs.CertDB(httpinstance.NSS_DIR, host_name=self.fqdn, subject_base=self.subject_base)
if self.self_signed_ca:
cadb.create_self_signed()
dsdb.create_from_cacert(cadb.cacert_fname, passwd=None)
- self.dercert = dsdb.create_server_cert("Server-Cert", self.host_name, cadb)
+ self.dercert = dsdb.create_server_cert("Server-Cert", self.fqdn, cadb)
+ dsdb.track_server_cert("Server-Cert", self.principal, dsdb.passwd_fname)
dsdb.create_pin_file()
else:
# FIXME, need to set this nickname in the RA plugin
cadb.export_ca_cert('ipaCert', False)
dsdb.create_from_cacert(cadb.cacert_fname, passwd=None)
- self.dercert = dsdb.create_server_cert("Server-Cert", self.host_name, cadb)
+ self.dercert = dsdb.create_server_cert("Server-Cert", self.fqdn, cadb)
+ dsdb.track_server_cert("Server-Cert", self.principal, dsdb.passwd_fname)
dsdb.create_pin_file()
conn = ipaldap.IPAdmin("127.0.0.1")
@@ -491,6 +493,9 @@ class DsInstance(service.Service):
serverid = self.restore_state("serverid")
if not serverid is None:
+ dirname = config_dirname(serverid)
+ dsdb = certs.CertDB(dirname)
+ dsdb.untrack_server_cert("Server-Cert")
erase_ds_instance_data(serverid)
ds_user = self.restore_state("user")
diff --git a/ipaserver/install/httpinstance.py b/ipaserver/install/httpinstance.py
index 48a908f15..af8fdde18 100644
--- a/ipaserver/install/httpinstance.py
+++ b/ipaserver/install/httpinstance.py
@@ -120,10 +120,9 @@ class HTTPInstance(service.Service):
self.print_msg(selinux_warning)
def __create_http_keytab(self):
- http_principal = "HTTP/" + self.fqdn + "@" + self.realm
- installutils.kadmin_addprinc(http_principal)
- installutils.create_keytab("/etc/httpd/conf/ipa.keytab", http_principal)
- self.move_service(http_principal)
+ installutils.kadmin_addprinc(self.principal)
+ installutils.create_keytab("/etc/httpd/conf/ipa.keytab", self.principal)
+ self.move_service(self.principal)
self.add_cert_to_service()
pent = pwd.getpwnam("apache")
@@ -186,9 +185,11 @@ class HTTPInstance(service.Service):
db.create_from_cacert(ca_db.cacert_fname)
db.create_password_conf()
self.dercert = db.create_server_cert("Server-Cert", self.fqdn, ca_db)
+ db.track_server_cert("Server-Cert", self.principal, db.passwd_fname)
db.create_signing_cert("Signing-Cert", "Object Signing Cert", ca_db)
else:
self.dercert = db.create_server_cert("Server-Cert", self.fqdn, ca_db)
+ db.track_server_cert("Server-Cert", self.principal, db.passwd_fname)
db.create_signing_cert("Signing-Cert", "Object Signing Cert", ca_db)
db.create_password_conf()
@@ -251,6 +252,8 @@ class HTTPInstance(service.Service):
if not running is None:
self.stop()
+ db = certs.CertDB(NSS_DIR)
+ db.untrack_server_cert("Server-Cert")
if not enabled is None and not enabled:
self.chkconfig_off()
diff --git a/ipaserver/install/service.py b/ipaserver/install/service.py
index 4958721e7..47489c09c 100644
--- a/ipaserver/install/service.py
+++ b/ipaserver/install/service.py
@@ -145,12 +145,14 @@ class Service:
conn.unbind()
return
newdn = "krbprincipalname=%s,cn=services,cn=accounts,%s" % (principal, self.suffix)
+ hostdn = "fqdn=%s,cn=computers,cn=accounts,%s" % (self.fqdn, self.suffix)
conn.deleteEntry(dn)
entry.dn = newdn
classes = entry.getValues("objectclass")
classes = classes + ["ipaobject", "ipaservice", "pkiuser"]
entry.setValues("objectclass", list(set(classes)))
entry.setValue("ipauniqueid", str(uuid.uuid1()))
+ entry.setValue("managedby", hostdn)
conn.addEntry(entry)
conn.unbind()
return newdn