diff options
author | Simo Sorce <simo@redhat.com> | 2016-07-26 11:19:01 -0400 |
---|---|---|
committer | Simo Sorce <simo@redhat.com> | 2016-12-08 19:54:30 -0500 |
commit | e17438cca414b1bc7a5c21da502550a520f25a67 (patch) | |
tree | e387e32f96a2893a1729a738cf7350b4b5a7611b /ipaserver | |
parent | fad87a9962ee33cfebc4fa59aba589e98b076cea (diff) | |
download | freeipa-kdc-pkinit.tar.gz freeipa-kdc-pkinit.tar.xz freeipa-kdc-pkinit.zip |
Configure Anonymous PKINIT on server installkdc-pkinit
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 <simo@redhat.com>
Diffstat (limited to 'ipaserver')
-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 |
11 files changed, 179 insertions, 51 deletions
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py index bf798211d..dbea519ee 100644 --- a/ipaserver/install/cainstance.py +++ b/ipaserver/install/cainstance.py @@ -831,7 +831,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..2b63b4ebd 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,15 @@ 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.__common_post_setup() + if setup_pkinit: + self.step("installing X509 Certificate for PKINIT", + self.setup_pkinit) + self.step("creating principal for anonymous PKINIT", + self.__add_anonymous_pkinit_principal) + self.start_creation(runtime=30) self.kpasswd = KpasswdInstance() @@ -179,7 +184,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 +220,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 +345,49 @@ 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) + + 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(self): princ = "WELLKNOWN/ANONYMOUS" - princ_realm = "%s@%s" % (princ, self.realm) + return "%s@%s" % (princ, self.realm) + def __add_anonymous_pkinit_principal(self): # Create the special anonymous principal + princ_realm = self.get_anonymous_principal() 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 f1f7b1bf8..0cd346849 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..5056f833e 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() + dn = DN(('krbprincipalname', princ_realm), krb.get_realm_suffix()) + try: + _ = api.Backend.ldap2.get_entry(dn) # pylint: disable=unused-variable + except ipalib.errors.NotFound: + installutils.kadmin_addprinc(princ_realm) + + 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() + enable_anonymous_principal(krb) + 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() + 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 0bdb4daf2..3d0838cdd 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 |