From ca4e6c1fdfac9b545b26f885dc4865f22ca36ae6 Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Dec 12 2016 12:39:44 +0000 Subject: Configure Anonymous PKINIT on server install Allow anonymous pkinit to be used so that unenrolled hosts can perform FAST authentication (necessary for 2FA for example) using an anonymous krbtgt obtained via Pkinit. https://fedorahosted.org/freeipa/ticket/5678 Signed-off-by: Simo Sorce Reviewed-By: Alexander Bokovoy Reviewed-By: Martin Babinsky --- diff --git a/install/share/kdc.conf.template b/install/share/kdc.conf.template index 296b75b..ec53a1f 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 0000000..c5e412b --- /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 d1c1bac..640ca0a 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 0eec5bd..ae96bc1 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 c79cf93..951ca9a 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 f85a2aa..896fa9d 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 f4f1955..eb1f73e 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 29acd7e..c7e81f0 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 45602ba..02b03d4 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 1be5ac7..bcfcb05 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 15c3107..b7ce857 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 8de92f7..b52b0c3 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 0237702..28cdd06 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 f81c202..b5b9cb4 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 06d209e..b0cf28f 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 2454507..0ebe9af 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 e4efa7d..81872cf 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 b77b21a..73c14ed 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