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 <simo@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Martin Babinsky <mbabinsk@redhat.com>
This commit is contained in:
Simo Sorce 2016-07-26 11:19:01 -04:00 committed by Jan Cholasta
parent 32b1743e5f
commit ca4e6c1fdf
18 changed files with 325 additions and 69 deletions

View File

@ -12,6 +12,6 @@
dict_file = $DICT_WORDS dict_file = $DICT_WORDS
default_principal_flags = +preauth default_principal_flags = +preauth
; admin_keytab = $KRB5KDC_KADM5_KEYTAB ; admin_keytab = $KRB5KDC_KADM5_KEYTAB
pkinit_identity = FILE:$KDC_PEM pkinit_identity = FILE:$KDC_CERT,$KDC_KEY
pkinit_anchors = FILE:$CACERT_PEM pkinit_anchors = FILE:$CACERT_PEM
} }

View File

@ -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

View File

@ -4,6 +4,7 @@ appdir = $(IPA_DATA_DIR)/profiles
app_DATA = \ app_DATA = \
caIPAserviceCert.cfg \ caIPAserviceCert.cfg \
IECUserRoles.cfg \ IECUserRoles.cfg \
KDCs_PKINIT_Certs.cfg \
$(NULL) $(NULL)
EXTRA_DIST = \ EXTRA_DIST = \

View File

@ -779,7 +779,7 @@ def configure_certmonger(
passwd_fname = os.path.join(paths.IPA_NSSDB_DIR, 'pwdfile.txt') passwd_fname = os.path.join(paths.IPA_NSSDB_DIR, 'pwdfile.txt')
try: try:
certmonger.request_cert( certmonger.request_cert(
nssdb=paths.IPA_NSSDB_DIR, certpath=paths.IPA_NSSDB_DIR,
nickname='Local IPA host', subject=subject, dns=[hostname], nickname='Local IPA host', subject=subject, dns=[hostname],
principal=principal, passwd_fname=passwd_fname) principal=principal, passwd_fname=passwd_fname)
except Exception as ex: except Exception as ex:

View File

@ -299,17 +299,17 @@ def add_subject(request_id, subject):
def request_and_wait_for_cert( 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, 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. Execute certmonger to request a server certificate.
The method also waits for the certificate to be available. 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, passwd_fname, dns, ca, profile,
pre_command, post_command) pre_command, post_command, storage)
state = wait_for_request(reqId, api.env.startup_timeout) state = wait_for_request(reqId, api.env.startup_timeout)
ca_error = get_request_value(reqId, 'ca-error') ca_error = get_request_value(reqId, 'ca-error')
if state != 'MONITORING' or ca_error: if state != 'MONITORING' or ca_error:
@ -318,23 +318,29 @@ def request_and_wait_for_cert(
def request_cert( def request_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): dns=None, ca='IPA', profile=None,
pre_command=None, post_command=None, storage='NSSDB'):
""" """
Execute certmonger to request a server certificate. Execute certmonger to request a server certificate.
``dns`` ``dns``
A sequence of DNS names to appear in SAN request extension. A sequence of DNS names to appear in SAN request extension.
""" """
if storage == 'FILE':
certfile, keyfile = certpath
else:
certfile = certpath
keyfile = certpath
cm = _certmonger() cm = _certmonger()
ca_path = cm.obj_if.find_ca_by_nickname(ca) ca_path = cm.obj_if.find_ca_by_nickname(ca)
if not ca_path: if not ca_path:
raise RuntimeError('{} CA not found'.format(ca)) raise RuntimeError('{} CA not found'.format(ca))
request_parameters = dict(KEY_STORAGE='NSSDB', CERT_STORAGE='NSSDB', request_parameters = dict(KEY_STORAGE=storage, CERT_STORAGE=storage,
CERT_LOCATION=nssdb, CERT_NICKNAME=nickname, CERT_LOCATION=certfile, CERT_NICKNAME=nickname,
KEY_LOCATION=nssdb, KEY_NICKNAME=nickname, KEY_LOCATION=keyfile, KEY_NICKNAME=nickname,
SUBJECT=subject, SUBJECT=subject, CA=ca_path)
CA=ca_path)
if principal: if principal:
request_parameters['PRINCIPAL'] = [principal] request_parameters['PRINCIPAL'] = [principal]
if dns is not None and len(dns) > 0: 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') 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. Stop tracking the current request using either the request_id or nickname.
Returns True or False Returns True or False
""" """
if request_id is None and nickname is None: if request_id is None and nickname is None and certfile is None:
raise RuntimeError('Both request_id and nickname are missing.') 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: if request_id:
criteria['nickname'] = request_id criteria['nickname'] = request_id
if nickname: if nickname:
criteria['cert-nickname'] = nickname criteria['cert-nickname'] = nickname
if certfile:
criteria['cert-file'] = certfile
try: try:
request = _get_request(criteria) request = _get_request(criteria)
except RuntimeError as e: except RuntimeError as e:

View File

@ -240,7 +240,8 @@ class BasePathNamespace(object):
KRB5KDC_KADM5_ACL = "/var/kerberos/krb5kdc/kadm5.acl" KRB5KDC_KADM5_ACL = "/var/kerberos/krb5kdc/kadm5.acl"
KRB5KDC_KADM5_KEYTAB = "/var/kerberos/krb5kdc/kadm5.keytab" KRB5KDC_KADM5_KEYTAB = "/var/kerberos/krb5kdc/kadm5.keytab"
KRB5KDC_KDC_CONF = "/var/kerberos/krb5kdc/kdc.conf" 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" VAR_LIB = "/var/lib"
AUTHCONFIG_LAST = "/var/lib/authconfig/last" AUTHCONFIG_LAST = "/var/lib/authconfig/last"
VAR_LIB_CERTMONGER_DIR = "/var/lib/certmonger" VAR_LIB_CERTMONGER_DIR = "/var/lib/certmonger"

View File

@ -48,9 +48,13 @@ Profile = collections.namedtuple('Profile', ['profile_id', 'description', 'store
INCLUDED_PROFILES = { INCLUDED_PROFILES = {
Profile(u'caIPAserviceCert', u'Standard profile for network services', True), 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'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' DEFAULT_PROFILE = u'caIPAserviceCert'
KDC_PROFILE = u'KDCs_PKINIT_Certs'
def error_from_xml(doc, message_template): def error_from_xml(doc, message_template):

View File

@ -817,7 +817,7 @@ class CAInstance(DogtagInstance):
# The certificate must be requested using caServerCert profile # The certificate must be requested using caServerCert profile
# because this profile does not require agent authentication # because this profile does not require agent authentication
reqId = certmonger.request_and_wait_for_cert( reqId = certmonger.request_and_wait_for_cert(
nssdb=self.ra_agent_db, certpath=self.ra_agent_db,
nickname='ipaCert', nickname='ipaCert',
principal='host/%s' % self.fqdn, principal='host/%s' % self.fqdn,
passwd_fname=self.ra_agent_pwd, passwd_fname=self.ra_agent_pwd,

View File

@ -633,7 +633,13 @@ class CertDB(object):
def install_pem_from_p12(self, p12_fname, p12_passwd, pem_fname): def install_pem_from_p12(self, p12_fname, p12_passwd, pem_fname):
pwd = ipautil.write_tmp_file(p12_passwd) 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, "-in", p12_fname, "-out", pem_fname,
"-passin", "file:" + pwd.name]) "-passin", "file:" + pwd.name])
@ -647,7 +653,7 @@ class CertDB(object):
def request_service_cert(self, nickname, principal, host, pwdconf=False): def request_service_cert(self, nickname, principal, host, pwdconf=False):
if pwdconf: if pwdconf:
self.create_password_conf() self.create_password_conf()
certmonger.request_and_wait_for_cert(nssdb=self.secdir, certmonger.request_and_wait_for_cert(certpath=self.secdir,
nickname=nickname, nickname=nickname,
principal=principal, principal=principal,
subject=host, subject=host,

View File

@ -816,7 +816,7 @@ class DsInstance(service.Service):
try: try:
cmd = 'restart_dirsrv %s' % self.serverid cmd = 'restart_dirsrv %s' % self.serverid
certmonger.request_and_wait_for_cert( certmonger.request_and_wait_for_cert(
nssdb=dirname, certpath=dirname,
nickname=self.nickname, nickname=self.nickname,
principal=self.principal, principal=self.principal,
passwd_fname=dsdb.passwd_fname, passwd_fname=dsdb.passwd_fname,

View File

@ -376,7 +376,7 @@ class HTTPInstance(service.Service):
try: try:
certmonger.request_and_wait_for_cert( certmonger.request_and_wait_for_cert(
nssdb=db.secdir, certpath=db.secdir,
nickname=self.cert_nickname, nickname=self.cert_nickname,
principal=self.principal, principal=self.principal,
passwd_fname=db.passwd_fname, passwd_fname=db.passwd_fname,

View File

@ -24,6 +24,7 @@ import shutil
import os import os
import pwd import pwd
import socket import socket
import dbus
import dns.name import dns.name
@ -32,6 +33,7 @@ from ipaserver.install import installutils
from ipapython import ipautil from ipapython import ipautil
from ipapython import kernel_keyring from ipapython import kernel_keyring
from ipalib import api from ipalib import api
from ipalib.install import certmonger
from ipapython.ipa_log_manager import root_logger from ipapython.ipa_log_manager import root_logger
from ipapython.dn import DN 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 directory", self.__create_ds_keytab)
self.step("creating a keytab for the machine", self.__create_host_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) self.step("adding the password extension to the directory", self.__add_pwd_extop_module)
if setup_pkinit: self.step("creating anonymous principal", self.add_anonymous_principal)
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() self.__common_post_setup()
if setup_pkinit:
self.step("installing X509 Certificate for PKINIT",
self.setup_pkinit)
self.start_creation(runtime=30) self.start_creation(runtime=30)
self.kpasswd = KpasswdInstance() self.kpasswd = KpasswdInstance()
@ -179,7 +183,8 @@ class KrbInstance(service.Service):
self.step("configuring KDC", self.__configure_instance) self.step("configuring KDC", self.__configure_instance)
self.step("adding the password extension to the directory", self.__add_pwd_extop_module) self.step("adding the password extension to the directory", self.__add_pwd_extop_module)
if setup_pkinit: 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() self.__common_post_setup()
@ -214,7 +219,8 @@ class KrbInstance(service.Service):
KRB5KDC_KADM5_ACL=paths.KRB5KDC_KADM5_ACL, KRB5KDC_KADM5_ACL=paths.KRB5KDC_KADM5_ACL,
DICT_WORDS=paths.DICT_WORDS, DICT_WORDS=paths.DICT_WORDS,
KRB5KDC_KADM5_KEYTAB=paths.KRB5KDC_KADM5_KEYTAB, 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) CACERT_PEM=paths.CACERT_PEM)
# IPA server/KDC is not a subdomain of default domain # 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) 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, ca_db = certs.CertDB(self.realm, host_name=self.fqdn,
subject_base=self.subject_base) subject_base=self.subject_base)
if self.pkcs12_info: if self.pkcs12_info:
ca_db.install_pem_from_p12(self.pkcs12_info[0], ca_db.install_pem_from_p12(self.pkcs12_info[0],
self.pkcs12_info[1], 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: 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 # Finally copy the cacert in the krb directory so we don't
# have any selinux issues with the file context # have any selinux issues with the file context
shutil.copyfile(paths.IPA_CA_CRT, paths.CACERT_PEM) 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 = "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 # Create the special anonymous principal
princ_realm = self.get_anonymous_principal_name()
installutils.kadmin_addprinc(princ_realm) 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): def __convert_to_gssapi_replication(self):
repl = replication.ReplicationManager(self.realm, repl = replication.ReplicationManager(self.realm,
@ -372,6 +397,9 @@ class KrbInstance(service.Service):
r_binddn=DN(('cn', 'Directory Manager')), r_binddn=DN(('cn', 'Directory Manager')),
r_bindpw=self.dm_password) r_bindpw=self.dm_password)
def stop_tracking_certs(self):
certmonger.stop_tracking(certfile=paths.KDC_CERT)
def uninstall(self): def uninstall(self):
if self.is_configured(): if self.is_configured():
self.print_msg("Unconfiguring %s" % self.service_name) self.print_msg("Unconfiguring %s" % self.service_name)
@ -394,6 +422,12 @@ class KrbInstance(service.Service):
if enabled: if enabled:
self.enable() 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: if running:
self.restart() self.restart()

View File

@ -501,8 +501,8 @@ class ServerInstallInterface(client.ClientInstallInterface,
"You must specify at least one of --forwarder, " "You must specify at least one of --forwarder, "
"--auto-forwarders, or --no-forwarders options") "--auto-forwarders, or --no-forwarders options")
# Automatically disable pkinit w/ dogtag until that is supported # Automatically enable pkinit w/ dogtag
self.no_pkinit = True self.no_pkinit = not self.setup_ca
ServerMasterInstallInterface = installs_master(ServerInstallInterface) ServerMasterInstallInterface = installs_master(ServerInstallInterface)

View File

@ -521,6 +521,11 @@ def install_check(installer):
dirsrv_pkcs12_info = (dirsrv_pkcs12_file.name, dirsrv_pin) dirsrv_pkcs12_info = (dirsrv_pkcs12_file.name, dirsrv_pin)
if options.pkinit_cert_files: 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: if options.pkinit_pin is None:
options.pkinit_pin = read_password( options.pkinit_pin = read_password(
"Enter Kerberos KDC private key unlock", "Enter Kerberos KDC private key unlock",
@ -792,17 +797,11 @@ def install(installer):
ds.enable_ssl() ds.enable_ssl()
krb = krbinstance.KrbInstance(fstore) krb = krbinstance.KrbInstance(fstore)
if options.pkinit_cert_files:
krb.create_instance(realm_name, host_name, domain_name, krb.create_instance(realm_name, host_name, domain_name,
dm_password, master_password, dm_password, master_password,
setup_pkinit=not options.no_pkinit, setup_pkinit=not options.no_pkinit,
pkcs12_info=pkinit_pkcs12_info, pkcs12_info=pkinit_pkcs12_info,
subject_base=options.subject) 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)
# restart DS to enable ipa-pwd-extop plugin # restart DS to enable ipa-pwd-extop plugin
print("Restarting directory server to enable password extension plugin") print("Restarting directory server to enable password extension plugin")

View File

@ -124,7 +124,9 @@ def install_krb(config, setup_pkinit=False, promote=False):
krb.create_replica(config.realm_name, krb.create_replica(config.realm_name,
config.master_host_name, config.host_name, config.master_host_name, config.host_name,
config.domain_name, config.dirman_password, 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 return krb

View File

@ -47,6 +47,7 @@ from ipaserver.install import sysupgrade
from ipaserver.install import dnskeysyncinstance from ipaserver.install import dnskeysyncinstance
from ipaserver.install import krainstance from ipaserver.install import krainstance
from ipaserver.install import dogtaginstance from ipaserver.install import dogtaginstance
from ipaserver.install import krbinstance
from ipaserver.install.upgradeinstance import IPAUpgrade from ipaserver.install.upgradeinstance import IPAUpgrade
from ipaserver.install.ldapupdate import BadSyntax from ipaserver.install.ldapupdate import BadSyntax
@ -1492,6 +1493,20 @@ def add_default_caacl(ca):
sysupgrade.set_upgrade_state('caacl', 'add_default_caacl', True) 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(): def upgrade_configuration():
""" """
Execute configuration upgrade of the IPA services Execute configuration upgrade of the IPA services
@ -1735,6 +1750,26 @@ def upgrade_configuration():
set_sssd_domain_option('ipa_server_mode', 'True') 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: if not ds_running:
ds.stop(ds_serverid) ds.stop(ds_serverid)

View File

@ -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 = { PRINCIPAL_TYPE_STRING_MAP = {
USER: _('user'), USER: _('user'),
HOST: _('host'), HOST: _('host'),
KRBTGT: _('krbtgt'),
SERVICE: _('service'), 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): def validate_certificate(value):
return x509.validate_certificate(value, x509.DER) return x509.validate_certificate(value, x509.DER)
@ -533,6 +541,7 @@ class cert_request(Create, BaseCertMethod, VirtualCommand):
ca_enabled_check() ca_enabled_check()
ldap = self.api.Backend.ldap2 ldap = self.api.Backend.ldap2
realm = unicode(self.api.env.realm)
add = kw.get('add') add = kw.get('add')
request_type = kw.get('request_type') request_type = kw.get('request_type')
profile_id = kw.get('profile_id', self.Backend.ra.DEFAULT_PROFILE) profile_id = kw.get('profile_id', self.Backend.ra.DEFAULT_PROFILE)
@ -563,11 +572,16 @@ class cert_request(Create, BaseCertMethod, VirtualCommand):
principal_type = USER principal_type = USER
elif principal.is_host: elif principal.is_host:
principal_type = 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: else:
principal_type = SERVICE principal_type = SERVICE
bind_principal = kerberos.Principal( bind_principal = kerberos.Principal(getattr(context, 'principal'))
getattr(context, 'principal'))
bind_principal_string = unicode(bind_principal) bind_principal_string = unicode(bind_principal)
if bind_principal.is_user: if bind_principal.is_user:
@ -589,6 +603,9 @@ class cert_request(Create, BaseCertMethod, VirtualCommand):
bypass_caacl = False bypass_caacl = False
if not bypass_caacl: if not bypass_caacl:
if principal_type == KRBTGT:
ca_kdc_check(ldap, bind_principal.hostname)
else:
caacl_check(principal_type, principal, ca, profile_id) caacl_check(principal_type, principal, ca, profile_id)
try: try:
@ -616,6 +633,11 @@ class cert_request(Create, BaseCertMethod, VirtualCommand):
try: try:
if principal_type == SERVICE: if principal_type == SERVICE:
principal_obj = api.Command['service_show'](principal_string, all=True) 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: elif principal_type == HOST:
principal_obj = api.Command['host_show']( principal_obj = api.Command['host_show'](
principal.hostname, all=True) principal.hostname, all=True)
@ -635,6 +657,7 @@ class cert_request(Create, BaseCertMethod, VirtualCommand):
else: else:
raise errors.NotFound( raise errors.NotFound(
reason=_("The principal for this request doesn't exist.")) reason=_("The principal for this request doesn't exist."))
if principal_obj:
principal_obj = principal_obj['result'] principal_obj = principal_obj['result']
dn = principal_obj['dn'] dn = principal_obj['dn']
@ -656,6 +679,13 @@ class cert_request(Create, BaseCertMethod, VirtualCommand):
"hostname in subject of request '%(cn)s' does not " "hostname in subject of request '%(cn)s' does not "
"match name or aliases of principal '%(principal)s'" "match name or aliases of principal '%(principal)s'"
) % dict(cn=cn, principal=principal)) ) % 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: elif principal_type == USER:
# check user name # check user name
if cn != principal.username: if cn != principal.username:
@ -677,10 +707,12 @@ class cert_request(Create, BaseCertMethod, VirtualCommand):
"any of user's email addresses") "any of user's email addresses")
) )
if principal_type != KRBTGT:
# We got this far so the principal entry exists, can we write it? # We got this far so the principal entry exists, can we write it?
if not ldap.can_write(dn, "usercertificate"): if not ldap.can_write(dn, "usercertificate"):
raise errors.ACIError(info=_("Insufficient 'write' privilege " raise errors.ACIError(
"to the 'userCertificate' attribute of entry '%s'.") % dn) info=_("Insufficient 'write' privilege to the "
"'userCertificate' attribute of entry '%s'.") % dn)
# Validate the subject alt name, if any # Validate the subject alt name, if any
generalnames = [] generalnames = []
@ -711,6 +743,9 @@ class cert_request(Create, BaseCertMethod, VirtualCommand):
if principal_type == HOST: if principal_type == HOST:
alt_principal_obj = api.Command['host_show']( alt_principal_obj = api.Command['host_show'](
name, all=True) name, all=True)
elif principal_type == KRBTGT:
alt_principal = kerberos.Principal(
(u'host', name), principal.realm)
elif principal_type == SERVICE: elif principal_type == SERVICE:
alt_principal_obj = api.Command['service_show']( alt_principal_obj = api.Command['service_show'](
alt_principal, all=True) alt_principal, all=True)
@ -722,6 +757,7 @@ class cert_request(Create, BaseCertMethod, VirtualCommand):
'subject alt name %s in certificate request does not ' 'subject alt name %s in certificate request does not '
'exist') % name) 'exist') % name)
if alt_principal_obj is not None:
# we found an alternative principal; # we found an alternative principal;
# now check write access and caacl # now check write access and caacl
altdn = alt_principal_obj['result']['dn'] altdn = alt_principal_obj['result']['dn']
@ -730,9 +766,17 @@ class cert_request(Create, BaseCertMethod, VirtualCommand):
"Insufficient privilege to create a certificate " "Insufficient privilege to create a certificate "
"with subject alt name '%s'.") % name) "with subject alt name '%s'.") % name)
if not bypass_caacl: 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)): 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( if not _principal_name_matches_principal(
gn.name, principal_obj): gn.name, principal_obj):
raise errors.ValidationError( raise errors.ValidationError(
@ -793,6 +837,9 @@ class cert_request(Create, BaseCertMethod, VirtualCommand):
api.Command['host_mod'](principal.hostname, **kwargs) api.Command['host_mod'](principal.hostname, **kwargs)
elif principal_type == USER: elif principal_type == USER:
api.Command['user_mod'](principal.username, **kwargs) 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( return dict(
result=result, result=result,
@ -810,6 +857,9 @@ def _dns_name_matches_principal(name, principal, principal_obj):
:return: True if name matches, otherwise False :return: True if name matches, otherwise False
""" """
if principal_obj is None:
return False
for alias in principal_obj.get('krbprincipalname', []): for alias in principal_obj.get('krbprincipalname', []):
# we can only compare them if both subject principal and # we can only compare them if both subject principal and
# the alias are service or host principals # the alias are service or host principals

View File

@ -1225,6 +1225,8 @@ class RestClient(Backend):
profile_api.create_profile(...) profile_api.create_profile(...)
""" """
DEFAULT_PROFILE = dogtag.DEFAULT_PROFILE
KDC_PROFILE = dogtag.KDC_PROFILE
path = None path = None
@staticmethod @staticmethod