diff options
-rw-r--r-- | install/share/kdc.conf.template | 2 | ||||
-rw-r--r-- | install/share/profiles/KDCs_PKINIT_Certs.cfg | 109 | ||||
-rw-r--r-- | install/share/profiles/Makefile.am | 1 | ||||
-rw-r--r-- | ipaclient/install/client.py | 2 | ||||
-rw-r--r-- | ipalib/install/certmonger.py | 43 | ||||
-rw-r--r-- | ipaplatform/base/paths.py | 3 | ||||
-rw-r--r-- | ipapython/dogtag.py | 4 | ||||
-rw-r--r-- | ipaserver/install/cainstance.py | 2 | ||||
-rw-r--r-- | ipaserver/install/certs.py | 10 | ||||
-rw-r--r-- | ipaserver/install/dsinstance.py | 2 | ||||
-rw-r--r-- | ipaserver/install/httpinstance.py | 2 | ||||
-rw-r--r-- | ipaserver/install/krbinstance.py | 62 | ||||
-rw-r--r-- | ipaserver/install/server/__init__.py | 4 | ||||
-rw-r--r-- | ipaserver/install/server/install.py | 21 | ||||
-rw-r--r-- | ipaserver/install/server/replicainstall.py | 4 | ||||
-rw-r--r-- | ipaserver/install/server/upgrade.py | 35 | ||||
-rw-r--r-- | ipaserver/plugins/cert.py | 86 | ||||
-rw-r--r-- | ipaserver/plugins/dogtag.py | 2 |
18 files changed, 325 insertions, 69 deletions
diff --git a/install/share/kdc.conf.template b/install/share/kdc.conf.template index 296b75b4c..ec53a1ff5 100644 --- a/install/share/kdc.conf.template +++ b/install/share/kdc.conf.template @@ -12,6 +12,6 @@ dict_file = $DICT_WORDS default_principal_flags = +preauth ; admin_keytab = $KRB5KDC_KADM5_KEYTAB - pkinit_identity = FILE:$KDC_PEM + pkinit_identity = FILE:$KDC_CERT,$KDC_KEY pkinit_anchors = FILE:$CACERT_PEM } diff --git a/install/share/profiles/KDCs_PKINIT_Certs.cfg b/install/share/profiles/KDCs_PKINIT_Certs.cfg new file mode 100644 index 000000000..c5e412b00 --- /dev/null +++ b/install/share/profiles/KDCs_PKINIT_Certs.cfg @@ -0,0 +1,109 @@ +profileId=KDCs_PKINIT_Certs +classId=caEnrollImpl +desc=This certificate profile is for enrolling server certificates with IPA-RA agent authentication. +visible=false +enable=true +enableBy=admin +auth.instance_id=raCertAuth +name=IPA-RA Agent-Authenticated Server Certificate Enrollment +input.list=i1,i2 +input.i1.class_id=certReqInputImpl +input.i2.class_id=submitterInfoInputImpl +output.list=o1 +output.o1.class_id=certOutputImpl +policyset.list=serverCertSet +policyset.serverCertSet.list=1,2,3,4,5,6,7,8,9,10,11 +policyset.serverCertSet.1.constraint.class_id=subjectNameConstraintImpl +policyset.serverCertSet.1.constraint.name=Subject Name Constraint +policyset.serverCertSet.1.constraint.params.pattern=CN=[^,]+,.+ +policyset.serverCertSet.1.constraint.params.accept=true +policyset.serverCertSet.1.default.class_id=subjectNameDefaultImpl +policyset.serverCertSet.1.default.name=Subject Name Default +policyset.serverCertSet.1.default.params.name=CN=$$request.req_subject_name.cn$$, $SUBJECT_DN_O +policyset.serverCertSet.2.constraint.class_id=validityConstraintImpl +policyset.serverCertSet.2.constraint.name=Validity Constraint +policyset.serverCertSet.2.constraint.params.range=740 +policyset.serverCertSet.2.constraint.params.notBeforeCheck=false +policyset.serverCertSet.2.constraint.params.notAfterCheck=false +policyset.serverCertSet.2.default.class_id=validityDefaultImpl +policyset.serverCertSet.2.default.name=Validity Default +policyset.serverCertSet.2.default.params.range=731 +policyset.serverCertSet.2.default.params.startTime=0 +policyset.serverCertSet.3.constraint.class_id=keyConstraintImpl +policyset.serverCertSet.3.constraint.name=Key Constraint +policyset.serverCertSet.3.constraint.params.keyType=RSA +policyset.serverCertSet.3.constraint.params.keyParameters=2048,3072,4096 +policyset.serverCertSet.3.default.class_id=userKeyDefaultImpl +policyset.serverCertSet.3.default.name=Key Default +policyset.serverCertSet.4.constraint.class_id=noConstraintImpl +policyset.serverCertSet.4.constraint.name=No Constraint +policyset.serverCertSet.4.default.class_id=authorityKeyIdentifierExtDefaultImpl +policyset.serverCertSet.4.default.name=Authority Key Identifier Default +policyset.serverCertSet.5.constraint.class_id=noConstraintImpl +policyset.serverCertSet.5.constraint.name=No Constraint +policyset.serverCertSet.5.default.class_id=authInfoAccessExtDefaultImpl +policyset.serverCertSet.5.default.name=AIA Extension Default +policyset.serverCertSet.5.default.params.authInfoAccessADEnable_0=true +policyset.serverCertSet.5.default.params.authInfoAccessADLocationType_0=URIName +policyset.serverCertSet.5.default.params.authInfoAccessADLocation_0=http://$IPA_CA_RECORD.$DOMAIN/ca/ocsp +policyset.serverCertSet.5.default.params.authInfoAccessADMethod_0=1.3.6.1.5.5.7.48.1 +policyset.serverCertSet.5.default.params.authInfoAccessCritical=false +policyset.serverCertSet.5.default.params.authInfoAccessNumADs=1 +policyset.serverCertSet.6.constraint.class_id=keyUsageExtConstraintImpl +policyset.serverCertSet.6.constraint.name=Key Usage Extension Constraint +policyset.serverCertSet.6.constraint.params.keyUsageCritical=true +policyset.serverCertSet.6.constraint.params.keyUsageDigitalSignature=true +policyset.serverCertSet.6.constraint.params.keyUsageNonRepudiation=true +policyset.serverCertSet.6.constraint.params.keyUsageDataEncipherment=true +policyset.serverCertSet.6.constraint.params.keyUsageKeyEncipherment=true +policyset.serverCertSet.6.constraint.params.keyUsageKeyAgreement=false +policyset.serverCertSet.6.constraint.params.keyUsageKeyCertSign=false +policyset.serverCertSet.6.constraint.params.keyUsageCrlSign=false +policyset.serverCertSet.6.constraint.params.keyUsageEncipherOnly=false +policyset.serverCertSet.6.constraint.params.keyUsageDecipherOnly=false +policyset.serverCertSet.6.default.class_id=keyUsageExtDefaultImpl +policyset.serverCertSet.6.default.name=Key Usage Default +policyset.serverCertSet.6.default.params.keyUsageCritical=true +policyset.serverCertSet.6.default.params.keyUsageDigitalSignature=true +policyset.serverCertSet.6.default.params.keyUsageNonRepudiation=true +policyset.serverCertSet.6.default.params.keyUsageDataEncipherment=true +policyset.serverCertSet.6.default.params.keyUsageKeyEncipherment=true +policyset.serverCertSet.6.default.params.keyUsageKeyAgreement=false +policyset.serverCertSet.6.default.params.keyUsageKeyCertSign=false +policyset.serverCertSet.6.default.params.keyUsageCrlSign=false +policyset.serverCertSet.6.default.params.keyUsageEncipherOnly=false +policyset.serverCertSet.6.default.params.keyUsageDecipherOnly=false +policyset.serverCertSet.7.constraint.class_id=noConstraintImpl +policyset.serverCertSet.7.constraint.name=No Constraint +policyset.serverCertSet.7.default.class_id=extendedKeyUsageExtDefaultImpl +policyset.serverCertSet.7.default.name=Extended Key Usage Extension Default +policyset.serverCertSet.7.default.params.exKeyUsageCritical=false +policyset.serverCertSet.7.default.params.exKeyUsageOIDs=1.3.6.1.5.5.7.3.1,1.3.6.1.5.2.3.5 +policyset.serverCertSet.8.constraint.class_id=signingAlgConstraintImpl +policyset.serverCertSet.8.constraint.name=No Constraint +policyset.serverCertSet.8.constraint.params.signingAlgsAllowed=SHA1withRSA,SHA256withRSA,SHA512withRSA,SHA1withDSA,SHA1withEC,SHA256withEC,SHA384withEC,SHA512withEC +policyset.serverCertSet.8.default.class_id=signingAlgDefaultImpl +policyset.serverCertSet.8.default.name=Signing Alg +policyset.serverCertSet.8.default.params.signingAlg=- +policyset.serverCertSet.9.constraint.class_id=noConstraintImpl +policyset.serverCertSet.9.constraint.name=No Constraint +policyset.serverCertSet.9.default.class_id=crlDistributionPointsExtDefaultImpl +policyset.serverCertSet.9.default.name=CRL Distribution Points Extension Default +policyset.serverCertSet.9.default.params.crlDistPointsCritical=false +policyset.serverCertSet.9.default.params.crlDistPointsNum=1 +policyset.serverCertSet.9.default.params.crlDistPointsEnable_0=true +policyset.serverCertSet.9.default.params.crlDistPointsIssuerName_0=$CRL_ISSUER +policyset.serverCertSet.9.default.params.crlDistPointsIssuerType_0=DirectoryName +policyset.serverCertSet.9.default.params.crlDistPointsPointName_0=http://$IPA_CA_RECORD.$DOMAIN/ipa/crl/MasterCRL.bin +policyset.serverCertSet.9.default.params.crlDistPointsPointType_0=URIName +policyset.serverCertSet.9.default.params.crlDistPointsReasons_0= +policyset.serverCertSet.10.constraint.class_id=noConstraintImpl +policyset.serverCertSet.10.constraint.name=No Constraint +policyset.serverCertSet.10.default.class_id=subjectKeyIdentifierExtDefaultImpl +policyset.serverCertSet.10.default.name=Subject Key Identifier Extension Default +policyset.serverCertSet.10.default.params.critical=false +policyset.serverCertSet.11.constraint.class_id=noConstraintImpl +policyset.serverCertSet.11.constraint.name=No Constraint +policyset.serverCertSet.11.default.class_id=userExtensionDefaultImpl +policyset.serverCertSet.11.default.name=User Supplied Extension Default +policyset.serverCertSet.11.default.params.userExtOID=2.5.29.17 diff --git a/install/share/profiles/Makefile.am b/install/share/profiles/Makefile.am index d1c1bac96..640ca0a4a 100644 --- a/install/share/profiles/Makefile.am +++ b/install/share/profiles/Makefile.am @@ -4,6 +4,7 @@ appdir = $(IPA_DATA_DIR)/profiles app_DATA = \ caIPAserviceCert.cfg \ IECUserRoles.cfg \ + KDCs_PKINIT_Certs.cfg \ $(NULL) EXTRA_DIST = \ diff --git a/ipaclient/install/client.py b/ipaclient/install/client.py index 0eec5bdba..ae96bc191 100644 --- a/ipaclient/install/client.py +++ b/ipaclient/install/client.py @@ -779,7 +779,7 @@ def configure_certmonger( passwd_fname = os.path.join(paths.IPA_NSSDB_DIR, 'pwdfile.txt') try: certmonger.request_cert( - nssdb=paths.IPA_NSSDB_DIR, + certpath=paths.IPA_NSSDB_DIR, nickname='Local IPA host', subject=subject, dns=[hostname], principal=principal, passwd_fname=passwd_fname) except Exception as ex: diff --git a/ipalib/install/certmonger.py b/ipalib/install/certmonger.py index c79cf9383..951ca9ab8 100644 --- a/ipalib/install/certmonger.py +++ b/ipalib/install/certmonger.py @@ -299,17 +299,17 @@ def add_subject(request_id, subject): def request_and_wait_for_cert( - nssdb, nickname, subject, principal, passwd_fname=None, + certpath, nickname, subject, principal, passwd_fname=None, dns=None, ca='IPA', profile=None, - pre_command=None, post_command=None): + pre_command=None, post_command=None, storage='NSSDB'): """ Execute certmonger to request a server certificate. The method also waits for the certificate to be available. """ - reqId = request_cert(nssdb, nickname, subject, principal, + reqId = request_cert(certpath, nickname, subject, principal, passwd_fname, dns, ca, profile, - pre_command, post_command) + pre_command, post_command, storage) state = wait_for_request(reqId, api.env.startup_timeout) ca_error = get_request_value(reqId, 'ca-error') if state != 'MONITORING' or ca_error: @@ -318,23 +318,29 @@ def request_and_wait_for_cert( def request_cert( - nssdb, nickname, subject, principal, passwd_fname=None, - dns=None, ca='IPA', profile=None, pre_command=None, post_command=None): + certpath, nickname, subject, principal, passwd_fname=None, + dns=None, ca='IPA', profile=None, + pre_command=None, post_command=None, storage='NSSDB'): """ Execute certmonger to request a server certificate. ``dns`` A sequence of DNS names to appear in SAN request extension. """ + if storage == 'FILE': + certfile, keyfile = certpath + else: + certfile = certpath + keyfile = certpath + cm = _certmonger() ca_path = cm.obj_if.find_ca_by_nickname(ca) if not ca_path: raise RuntimeError('{} CA not found'.format(ca)) - request_parameters = dict(KEY_STORAGE='NSSDB', CERT_STORAGE='NSSDB', - CERT_LOCATION=nssdb, CERT_NICKNAME=nickname, - KEY_LOCATION=nssdb, KEY_NICKNAME=nickname, - SUBJECT=subject, - CA=ca_path) + request_parameters = dict(KEY_STORAGE=storage, CERT_STORAGE=storage, + CERT_LOCATION=certfile, CERT_NICKNAME=nickname, + KEY_LOCATION=keyfile, KEY_NICKNAME=nickname, + SUBJECT=subject, CA=ca_path) if principal: request_parameters['PRINCIPAL'] = [principal] if dns is not None and len(dns) > 0: @@ -409,20 +415,27 @@ def start_tracking(nickname, secdir, password_file=None, command=None): return request.prop_if.Get(DBUS_CM_REQUEST_IF, 'nickname') -def stop_tracking(secdir, request_id=None, nickname=None): +def stop_tracking(secdir=None, request_id=None, nickname=None, certfile=None): """ Stop tracking the current request using either the request_id or nickname. Returns True or False """ - if request_id is None and nickname is None: - raise RuntimeError('Both request_id and nickname are missing.') + if request_id is None and nickname is None and certfile is None: + raise RuntimeError('One of request_id, nickname and certfile is' + ' required.') + if secdir is not None and certfile is not None: + raise RuntimeError("Can't specify both secdir and certfile.") - criteria = {'cert-database': secdir} + criteria = dict() + if secdir: + criteria['cert-database'] = secdir if request_id: criteria['nickname'] = request_id if nickname: criteria['cert-nickname'] = nickname + if certfile: + criteria['cert-file'] = certfile try: request = _get_request(criteria) except RuntimeError as e: diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py index f85a2aa12..896fa9d98 100644 --- a/ipaplatform/base/paths.py +++ b/ipaplatform/base/paths.py @@ -240,7 +240,8 @@ class BasePathNamespace(object): KRB5KDC_KADM5_ACL = "/var/kerberos/krb5kdc/kadm5.acl" KRB5KDC_KADM5_KEYTAB = "/var/kerberos/krb5kdc/kadm5.keytab" KRB5KDC_KDC_CONF = "/var/kerberos/krb5kdc/kdc.conf" - KDC_PEM = "/var/kerberos/krb5kdc/kdc.pem" + KDC_CERT = "/var/kerberos/krb5kdc/kdc.crt" + KDC_KEY = "/var/kerberos/krb5kdc/kdc.key" VAR_LIB = "/var/lib" AUTHCONFIG_LAST = "/var/lib/authconfig/last" VAR_LIB_CERTMONGER_DIR = "/var/lib/certmonger" diff --git a/ipapython/dogtag.py b/ipapython/dogtag.py index f4f1955eb..eb1f73eee 100644 --- a/ipapython/dogtag.py +++ b/ipapython/dogtag.py @@ -48,9 +48,13 @@ Profile = collections.namedtuple('Profile', ['profile_id', 'description', 'store INCLUDED_PROFILES = { Profile(u'caIPAserviceCert', u'Standard profile for network services', True), Profile(u'IECUserRoles', u'User profile that includes IECUserRoles extension from request', True), + Profile(u'KDCs_PKINIT_Certs', + u'Profile for PKINIT support by KDCs', + False), } DEFAULT_PROFILE = u'caIPAserviceCert' +KDC_PROFILE = u'KDCs_PKINIT_Certs' def error_from_xml(doc, message_template): diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py index 29acd7e27..c7e81f085 100644 --- a/ipaserver/install/cainstance.py +++ b/ipaserver/install/cainstance.py @@ -817,7 +817,7 @@ class CAInstance(DogtagInstance): # The certificate must be requested using caServerCert profile # because this profile does not require agent authentication reqId = certmonger.request_and_wait_for_cert( - nssdb=self.ra_agent_db, + certpath=self.ra_agent_db, nickname='ipaCert', principal='host/%s' % self.fqdn, passwd_fname=self.ra_agent_pwd, diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py index 45602baa6..02b03d48b 100644 --- a/ipaserver/install/certs.py +++ b/ipaserver/install/certs.py @@ -633,7 +633,13 @@ class CertDB(object): def install_pem_from_p12(self, p12_fname, p12_passwd, pem_fname): pwd = ipautil.write_tmp_file(p12_passwd) - ipautil.run([paths.OPENSSL, "pkcs12", "-nodes", + ipautil.run([paths.OPENSSL, "pkcs12", "-nokeys", + "-in", p12_fname, "-out", pem_fname, + "-passin", "file:" + pwd.name]) + + def install_key_from_p12(self, p12_fname, p12_passwd, pem_fname): + pwd = ipautil.write_tmp_file(p12_passwd) + ipautil.run([paths.OPENSSL, "pkcs12", "-nodes", "-nocerts", "-in", p12_fname, "-out", pem_fname, "-passin", "file:" + pwd.name]) @@ -647,7 +653,7 @@ class CertDB(object): def request_service_cert(self, nickname, principal, host, pwdconf=False): if pwdconf: self.create_password_conf() - certmonger.request_and_wait_for_cert(nssdb=self.secdir, + certmonger.request_and_wait_for_cert(certpath=self.secdir, nickname=nickname, principal=principal, subject=host, diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py index 1be5ac73c..bcfcb0500 100644 --- a/ipaserver/install/dsinstance.py +++ b/ipaserver/install/dsinstance.py @@ -816,7 +816,7 @@ class DsInstance(service.Service): try: cmd = 'restart_dirsrv %s' % self.serverid certmonger.request_and_wait_for_cert( - nssdb=dirname, + certpath=dirname, nickname=self.nickname, principal=self.principal, passwd_fname=dsdb.passwd_fname, diff --git a/ipaserver/install/httpinstance.py b/ipaserver/install/httpinstance.py index 15c310780..b7ce857ed 100644 --- a/ipaserver/install/httpinstance.py +++ b/ipaserver/install/httpinstance.py @@ -376,7 +376,7 @@ class HTTPInstance(service.Service): try: certmonger.request_and_wait_for_cert( - nssdb=db.secdir, + certpath=db.secdir, nickname=self.cert_nickname, principal=self.principal, passwd_fname=db.passwd_fname, diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py index 8de92f764..b52b0c3f9 100644 --- a/ipaserver/install/krbinstance.py +++ b/ipaserver/install/krbinstance.py @@ -24,6 +24,7 @@ import shutil import os import pwd import socket +import dbus import dns.name @@ -32,6 +33,7 @@ from ipaserver.install import installutils from ipapython import ipautil from ipapython import kernel_keyring from ipalib import api +from ipalib.install import certmonger from ipapython.ipa_log_manager import root_logger from ipapython.dn import DN @@ -153,12 +155,14 @@ class KrbInstance(service.Service): self.step("creating a keytab for the directory", self.__create_ds_keytab) self.step("creating a keytab for the machine", self.__create_host_keytab) self.step("adding the password extension to the directory", self.__add_pwd_extop_module) - if setup_pkinit: - self.step("creating X509 Certificate for PKINIT", self.__setup_pkinit) - self.step("creating principal for anonymous PKINIT", self.__add_anonymous_pkinit_principal) + self.step("creating anonymous principal", self.add_anonymous_principal) self.__common_post_setup() + if setup_pkinit: + self.step("installing X509 Certificate for PKINIT", + self.setup_pkinit) + self.start_creation(runtime=30) self.kpasswd = KpasswdInstance() @@ -179,7 +183,8 @@ class KrbInstance(service.Service): self.step("configuring KDC", self.__configure_instance) self.step("adding the password extension to the directory", self.__add_pwd_extop_module) if setup_pkinit: - self.step("installing X509 Certificate for PKINIT", self.__setup_pkinit) + self.step("installing X509 Certificate for PKINIT", + self.setup_pkinit) self.__common_post_setup() @@ -214,7 +219,8 @@ class KrbInstance(service.Service): KRB5KDC_KADM5_ACL=paths.KRB5KDC_KADM5_ACL, DICT_WORDS=paths.DICT_WORDS, KRB5KDC_KADM5_KEYTAB=paths.KRB5KDC_KADM5_KEYTAB, - KDC_PEM=paths.KDC_PEM, + KDC_CERT=paths.KDC_CERT, + KDC_KEY=paths.KDC_KEY, CACERT_PEM=paths.CACERT_PEM) # IPA server/KDC is not a subdomain of default domain @@ -338,31 +344,50 @@ class KrbInstance(service.Service): self.move_service_to_host(host_principal) - def __setup_pkinit(self): + def setup_pkinit(self): ca_db = certs.CertDB(self.realm, host_name=self.fqdn, subject_base=self.subject_base) if self.pkcs12_info: ca_db.install_pem_from_p12(self.pkcs12_info[0], self.pkcs12_info[1], - paths.KDC_PEM) + paths.KDC_CERT) + ca_db.install_key_from_p12(self.pkcs12_info[0], + self.pkcs12_info[1], + paths.KDC_KEY) else: - raise RuntimeError("PKI not supported yet\n") + subject = str(DN(('cn', self.fqdn), self.subject_base)) + krbtgt = "krbtgt/" + self.realm + "@" + self.realm + certpath = (paths.KDC_CERT, paths.KDC_KEY) + try: + reqid = certmonger.request_cert(certpath, u'KDC-Cert', + subject, krbtgt, + dns=self.fqdn, storage='FILE', + profile='KDCs_PKINIT_Certs') + except dbus.DBusException as e: + # if the certificate is already tracked, ignore the error + name = e.get_dbus_name() + if name != 'org.fedorahosted.certmonger.duplicate': + root_logger.error("Failed to initiate the request: %s", e) + return + + try: + certmonger.wait_for_request(reqid) + except RuntimeError as e: + root_logger.error("Failed to wait for request: %s", e) # Finally copy the cacert in the krb directory so we don't # have any selinux issues with the file context shutil.copyfile(paths.IPA_CA_CRT, paths.CACERT_PEM) - def __add_anonymous_pkinit_principal(self): + def get_anonymous_principal_name(self): princ = "WELLKNOWN/ANONYMOUS" - princ_realm = "%s@%s" % (princ, self.realm) + return "%s@%s" % (princ, self.realm) + def add_anonymous_principal(self): # Create the special anonymous principal + princ_realm = self.get_anonymous_principal_name() installutils.kadmin_addprinc(princ_realm) - dn = DN(('krbprincipalname', princ_realm), self.get_realm_suffix()) - entry = api.Backend.ldap2.get_entry(dn) - entry['nsAccountlock'] = ['TRUE'] - api.Backend.ldap2.update_entry(entry) def __convert_to_gssapi_replication(self): repl = replication.ReplicationManager(self.realm, @@ -372,6 +397,9 @@ class KrbInstance(service.Service): r_binddn=DN(('cn', 'Directory Manager')), r_bindpw=self.dm_password) + def stop_tracking_certs(self): + certmonger.stop_tracking(certfile=paths.KDC_CERT) + def uninstall(self): if self.is_configured(): self.print_msg("Unconfiguring %s" % self.service_name) @@ -394,6 +422,12 @@ class KrbInstance(service.Service): if enabled: self.enable() + # stop tracking and remove certificates + self.stop_tracking_certs() + installutils.remove_file(paths.CACERT_PEM) + installutils.remove_file(paths.KDC_CERT) + installutils.remove_file(paths.KDC_KEY) + if running: self.restart() diff --git a/ipaserver/install/server/__init__.py b/ipaserver/install/server/__init__.py index 0237702cc..28cdd066a 100644 --- a/ipaserver/install/server/__init__.py +++ b/ipaserver/install/server/__init__.py @@ -501,8 +501,8 @@ class ServerInstallInterface(client.ClientInstallInterface, "You must specify at least one of --forwarder, " "--auto-forwarders, or --no-forwarders options") - # Automatically disable pkinit w/ dogtag until that is supported - self.no_pkinit = True + # Automatically enable pkinit w/ dogtag + self.no_pkinit = not self.setup_ca ServerMasterInstallInterface = installs_master(ServerInstallInterface) diff --git a/ipaserver/install/server/install.py b/ipaserver/install/server/install.py index f81c202cc..b5b9cb48a 100644 --- a/ipaserver/install/server/install.py +++ b/ipaserver/install/server/install.py @@ -521,6 +521,11 @@ def install_check(installer): dirsrv_pkcs12_info = (dirsrv_pkcs12_file.name, dirsrv_pin) if options.pkinit_cert_files: + if not options.no_pkinit: + raise ScriptError("Cannot create KDC PKINIT certificate and use " + "provided external PKINIT certificate at the " + "same time. Please choose one of them.") + if options.pkinit_pin is None: options.pkinit_pin = read_password( "Enter Kerberos KDC private key unlock", @@ -792,17 +797,11 @@ def install(installer): ds.enable_ssl() krb = krbinstance.KrbInstance(fstore) - if options.pkinit_cert_files: - krb.create_instance(realm_name, host_name, domain_name, - dm_password, master_password, - setup_pkinit=not options.no_pkinit, - pkcs12_info=pkinit_pkcs12_info, - subject_base=options.subject) - else: - krb.create_instance(realm_name, host_name, domain_name, - dm_password, master_password, - setup_pkinit=not options.no_pkinit, - subject_base=options.subject) + krb.create_instance(realm_name, host_name, domain_name, + dm_password, master_password, + setup_pkinit=not options.no_pkinit, + pkcs12_info=pkinit_pkcs12_info, + subject_base=options.subject) # restart DS to enable ipa-pwd-extop plugin print("Restarting directory server to enable password extension plugin") diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py index 06d209e23..b0cf28f0f 100644 --- a/ipaserver/install/server/replicainstall.py +++ b/ipaserver/install/server/replicainstall.py @@ -124,7 +124,9 @@ def install_krb(config, setup_pkinit=False, promote=False): krb.create_replica(config.realm_name, config.master_host_name, config.host_name, config.domain_name, config.dirman_password, - setup_pkinit, pkcs12_info, promote=promote) + setup_pkinit, pkcs12_info, + subject_base=config.subject_base, + promote=promote) return krb diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py index 245450701..0ebe9af24 100644 --- a/ipaserver/install/server/upgrade.py +++ b/ipaserver/install/server/upgrade.py @@ -47,6 +47,7 @@ from ipaserver.install import sysupgrade from ipaserver.install import dnskeysyncinstance from ipaserver.install import krainstance from ipaserver.install import dogtaginstance +from ipaserver.install import krbinstance from ipaserver.install.upgradeinstance import IPAUpgrade from ipaserver.install.ldapupdate import BadSyntax @@ -1492,6 +1493,20 @@ def add_default_caacl(ca): sysupgrade.set_upgrade_state('caacl', 'add_default_caacl', True) +def enable_anonymous_principal(krb): + princ_realm = krb.get_anonymous_principal_name() + dn = DN(('krbprincipalname', princ_realm), krb.get_realm_suffix()) + try: + _ = api.Backend.ldap2.get_entry(dn) # pylint: disable=unused-variable + except ipalib.errors.NotFound: + krb.add_anonymous_principal() + + try: + api.Backend.ldap2.set_entry_active(dn, True) + except ipalib.errors.AlreadyActive: + pass + + def upgrade_configuration(): """ Execute configuration upgrade of the IPA services @@ -1735,6 +1750,26 @@ def upgrade_configuration(): set_sssd_domain_option('ipa_server_mode', 'True') + krb = krbinstance.KrbInstance(fstore) + krb.fqdn = fqdn + krb.realm = api.env.realm + krb.suffix = ipautil.realm_to_suffix(krb.realm) + krb.subject_base = subject_base + if not os.path.exists(paths.KDC_CERT): + krb.setup_pkinit() + replacevars = dict() + replacevars['pkinit_identity'] = 'FILE:{},{}'.format( + paths.KDC_CERT,paths.KDC_KEY) + appendvars = {} + ipautil.backup_config_and_replace_variables( + fstore, paths.KRB5KDC_KDC_CONF, replacevars=replacevars, + appendvars=appendvars) + tasks.restore_context(paths.KRB5KDC_KDC_CONF) + if krb.is_running(): + krb.stop() + krb.start() + enable_anonymous_principal(krb) + if not ds_running: ds.stop(ds_serverid) diff --git a/ipaserver/plugins/cert.py b/ipaserver/plugins/cert.py index e4efa7d37..81872cffd 100644 --- a/ipaserver/plugins/cert.py +++ b/ipaserver/plugins/cert.py @@ -144,11 +144,12 @@ http://www.ietf.org/rfc/rfc5280.txt """) -USER, HOST, SERVICE = range(3) +USER, HOST, KRBTGT, SERVICE = range(4) PRINCIPAL_TYPE_STRING_MAP = { USER: _('user'), HOST: _('host'), + KRBTGT: _('krbtgt'), SERVICE: _('service'), } @@ -216,6 +217,13 @@ def caacl_check(principal_type, principal, ca, profile_id): ) +def ca_kdc_check(ldap, hostname): + result = api.Command.config_show()['result'] + if hostname not in result['ipa_master_server']: + raise errors.ACIError(info=_( + "Host '%(hostname)s' is not a KDC") % dict(hostname=hostname)) + + def validate_certificate(value): return x509.validate_certificate(value, x509.DER) @@ -533,6 +541,7 @@ class cert_request(Create, BaseCertMethod, VirtualCommand): ca_enabled_check() ldap = self.api.Backend.ldap2 + realm = unicode(self.api.env.realm) add = kw.get('add') request_type = kw.get('request_type') profile_id = kw.get('profile_id', self.Backend.ra.DEFAULT_PROFILE) @@ -563,11 +572,16 @@ class cert_request(Create, BaseCertMethod, VirtualCommand): principal_type = USER elif principal.is_host: principal_type = HOST + elif principal.service_name == 'krbtgt': + principal_type = KRBTGT + if profile_id != self.Backend.ra.KDC_PROFILE: + raise errors.ACIError( + info=_("krbtgt certs can use only the %s profile") % ( + self.Backend.ra.KDC_PROFILE)) else: principal_type = SERVICE - bind_principal = kerberos.Principal( - getattr(context, 'principal')) + bind_principal = kerberos.Principal(getattr(context, 'principal')) bind_principal_string = unicode(bind_principal) if bind_principal.is_user: @@ -589,7 +603,10 @@ class cert_request(Create, BaseCertMethod, VirtualCommand): bypass_caacl = False if not bypass_caacl: - caacl_check(principal_type, principal, ca, profile_id) + if principal_type == KRBTGT: + ca_kdc_check(ldap, bind_principal.hostname) + else: + caacl_check(principal_type, principal, ca, profile_id) try: csr_obj = pkcs10.load_certificate_request(csr) @@ -616,6 +633,11 @@ class cert_request(Create, BaseCertMethod, VirtualCommand): try: if principal_type == SERVICE: principal_obj = api.Command['service_show'](principal_string, all=True) + elif principal_type == KRBTGT: + # Allow only our own realm krbtgt for now, no trusted realm's. + if principal != kerberos.Principal((u'krbtgt', realm), + realm=realm): + raise errors.NotFound("Not our realm's krbtgt") elif principal_type == HOST: principal_obj = api.Command['host_show']( principal.hostname, all=True) @@ -635,8 +657,9 @@ class cert_request(Create, BaseCertMethod, VirtualCommand): else: raise errors.NotFound( reason=_("The principal for this request doesn't exist.")) - principal_obj = principal_obj['result'] - dn = principal_obj['dn'] + if principal_obj: + principal_obj = principal_obj['result'] + dn = principal_obj['dn'] # Ensure that the DN in the CSR matches the principal # @@ -656,6 +679,13 @@ class cert_request(Create, BaseCertMethod, VirtualCommand): "hostname in subject of request '%(cn)s' does not " "match name or aliases of principal '%(principal)s'" ) % dict(cn=cn, principal=principal)) + elif principal_type == KRBTGT and not bypass_caacl: + if cn.lower() != bind_principal.hostname.lower(): + raise errors.ACIError( + info=_("hostname in subject of request '%(cn)s' " + "does not match principal hostname " + "'%(hostname)s'") % dict( + cn=cn, hostname=bind_principal.hostname)) elif principal_type == USER: # check user name if cn != principal.username: @@ -677,10 +707,12 @@ class cert_request(Create, BaseCertMethod, VirtualCommand): "any of user's email addresses") ) - # We got this far so the principal entry exists, can we write it? - if not ldap.can_write(dn, "usercertificate"): - raise errors.ACIError(info=_("Insufficient 'write' privilege " - "to the 'userCertificate' attribute of entry '%s'.") % dn) + if principal_type != KRBTGT: + # We got this far so the principal entry exists, can we write it? + if not ldap.can_write(dn, "usercertificate"): + raise errors.ACIError( + info=_("Insufficient 'write' privilege to the " + "'userCertificate' attribute of entry '%s'.") % dn) # Validate the subject alt name, if any generalnames = [] @@ -711,6 +743,9 @@ class cert_request(Create, BaseCertMethod, VirtualCommand): if principal_type == HOST: alt_principal_obj = api.Command['host_show']( name, all=True) + elif principal_type == KRBTGT: + alt_principal = kerberos.Principal( + (u'host', name), principal.realm) elif principal_type == SERVICE: alt_principal_obj = api.Command['service_show']( alt_principal, all=True) @@ -722,17 +757,26 @@ class cert_request(Create, BaseCertMethod, VirtualCommand): 'subject alt name %s in certificate request does not ' 'exist') % name) - # we found an alternative principal; - # now check write access and caacl - altdn = alt_principal_obj['result']['dn'] - if not ldap.can_write(altdn, "usercertificate"): - raise errors.ACIError(info=_( - "Insufficient privilege to create a certificate " - "with subject alt name '%s'.") % name) + if alt_principal_obj is not None: + # we found an alternative principal; + # now check write access and caacl + altdn = alt_principal_obj['result']['dn'] + if not ldap.can_write(altdn, "usercertificate"): + raise errors.ACIError(info=_( + "Insufficient privilege to create a certificate " + "with subject alt name '%s'.") % name) if not bypass_caacl: - caacl_check(principal_type, alt_principal, ca, profile_id) + if principal_type == KRBTGT: + ca_kdc_check(ldap, alt_principal.hostname) + else: + caacl_check(principal_type, alt_principal, ca, + profile_id) elif isinstance(gn, (x509.KRB5PrincipalName, x509.UPN)): + if principal_type == KRBTGT: + principal_obj = dict() + principal_obj['krbprincipalname'] = [ + kerberos.Principal((u'krbtgt', realm), realm)] if not _principal_name_matches_principal( gn.name, principal_obj): raise errors.ValidationError( @@ -793,6 +837,9 @@ class cert_request(Create, BaseCertMethod, VirtualCommand): api.Command['host_mod'](principal.hostname, **kwargs) elif principal_type == USER: api.Command['user_mod'](principal.username, **kwargs) + elif principal_type == KRBTGT: + self.log.error("Profiles used to store cert should't be " + "used for krbtgt certificates") return dict( result=result, @@ -810,6 +857,9 @@ def _dns_name_matches_principal(name, principal, principal_obj): :return: True if name matches, otherwise False """ + if principal_obj is None: + return False + for alias in principal_obj.get('krbprincipalname', []): # we can only compare them if both subject principal and # the alias are service or host principals diff --git a/ipaserver/plugins/dogtag.py b/ipaserver/plugins/dogtag.py index b77b21aa2..73c14ed53 100644 --- a/ipaserver/plugins/dogtag.py +++ b/ipaserver/plugins/dogtag.py @@ -1225,6 +1225,8 @@ class RestClient(Backend): profile_api.create_profile(...) """ + DEFAULT_PROFILE = dogtag.DEFAULT_PROFILE + KDC_PROFILE = dogtag.KDC_PROFILE path = None @staticmethod |