On Fri, Aug 19, 2016 at 08:09:33PM +1000, Fraser Tweedale wrote: > On Mon, Aug 15, 2016 at 10:54:25PM +1000, Fraser Tweedale wrote: > > On Mon, Aug 15, 2016 at 02:08:54PM +0200, Jan Cholasta wrote: > > > On 19.7.2016 12:05, Jan Cholasta wrote: > > > > On 19.7.2016 11:54, Fraser Tweedale wrote: > > > > > On Tue, Jul 19, 2016 at 09:36:17AM +0200, Jan Cholasta wrote: > > > > > > Hi, > > > > > > > > > > > > On 15.7.2016 07:05, Fraser Tweedale wrote: > > > > > > > On Fri, Jul 15, 2016 at 03:04:48PM +1000, Fraser Tweedale wrote: > > > > > > > > The attached patch is a work in progress for > > > > > > > > https://fedorahosted.org/freeipa/ticket/2614 (BZ 828866). > > > > > > > > > > > > > > > > I am sharing now to make the approach clear and solicit > > > > > > > > feedback. > > > > > > > > > > > > > > > > It has been tested for server install, replica install (with and > > > > > > > > without CA) and CA-replica install (all hosts running > > > > > > > > master+patch). > > > > > > > > > > > > > > > > Migration from earlier versions and server/replica/CA install > > > > > > > > on a > > > > > > > > CA-less deployment are not yet tested; these will be tested over > > > > > > > > coming days and patch will be tweaked as necessary. > > > > > > > > > > > > > > > > Commit message has a fair bit to say so I won't repeat here but > > > > > > > > let > > > > > > > > me know your questions and comments. > > > > > > > > > > > > > > > > Thanks, > > > > > > > > Fraser > > > > > > > > > > > > > > > It does help to attach the patch, of course ^_^ > > > > > > > > > > > > IMO explicit is better than implicit, so instead of introducing > > > > > > additional > > > > > > magic around --subject, I would rather add a new separate option for > > > > > > specifying the CA subject name (I think --ca-subject, for > > > > > > consistency > > > > > > with > > > > > > --ca-signing-algorithm). > > > > > > > > > > > The current situation - the --subject argument which specifies the > > > > > not the subject but the subject base, is confusing enough (to say > > > > > nothing of the limitations that give rise to the RFE). > > > > > > > > > > Retaining --subject for specifying the subject base and adding > > > > > --ca-subject for specifying the *actual* subject DN gets us over the > > > > > line in terms of the RFE, but does not make the installer less > > > > > confusing. This is why I made --subject accept the full subject DN, > > > > > with provisions to retain existing behaviour. > > > > > > > > > > IMO if we want to have separate arguments for subject DN and subject > > > > > base (I am not against it), let's bite the bullet and name arguments > > > > > accordingly. --subject should be used to specify full Subject DN, > > > > > --subject-base (or similar) for specifying subject base. > > > > > > > > IMHO --ca-subject is better than --subject, because it is more explicit > > > > whose subject name that is (the CA's). I agree that --subject should be > > > > deprecated and replaced with --subject-base. > > > > > > > > > > > > > > (I intentionally defer discussion of specific behaviour if one, none > > > > > or both are specified; let's resolve the question or renaming / > > > > > changing meaning of arguments first). > > > > > > > > > > > > > > > > By specifying the option you would override the default > > > > > > "CN=Certificate > > > > > > Authority,$SUBJECT_BASE" subject name. If --external-ca was not > > > > > > used, > > > > > > additional validation would be done to make sure the subject name > > > > > > meets > > > > > > Dogtag's expectations. Actually, it might make sense to always do > > > > > > the > > > > > > additional validation, to be able to print a warning that if a > > > > > > Dogtag-incompatible subject name is used, it won't be possible to > > > > > > change the > > > > > > CA cert chaining from externally signed to self-signed later. > > > > > > > > > > > > Honza > > > > > > Bump, any update on this? > > > > > I have an updated patch that fixes some issues Sebastian encountered > > in testing, but I've not yet tackled the main change requested by > > Honza (in brief: adding --ca-subject for specifying that, adding > > --subject-base for specifying that, and deprecating --subject; > > Sebastian, see discussion above and feel free to give your > > thoughts). I expect I'll get back onto this work within the next > > few days. > > > Update: I've got an updated version of patch almost ready for > review, but I'm still ironing out some wrinkles in replica > installation. > > Expect to be able to send it Monday or Tuesday for review. > Updated patch attached.
I expect it will take a while to review, test and get the ACK, but in any case: IMO it should not be merged until after ipa-4-4 branch gets created. Thanks, Fraser
From d49a6b113c45ecd0a6686a1821338f5cf91cb1f5 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Mon, 11 Jul 2016 12:57:11 +1000 Subject: [PATCH] Allow full customisability of IPA CA subject DN Currently only the "subject base" of the IPA CA subject DN can be customised via the installer's --subject option. The RDN "CN=Certificate Authority" is appended to form the subject DN, and this composition is widely assumed, hardcoded in many places. Some administrators need more control over the CA subject DN, especially to satisfy expectations of external CAs when the IPA CA is to be externally signed. This patch adds full customisability of the CA subject DN. In particular: - Deprecate the ambiguously named --subject option. - Add the --subject-base option for explicitly specifying the subject base. If not given, fall back to --subject, then to "O=$REALM". - Add the --ca-subject option for explictly specifying the CA subject DN. If not given, default to "CN=Certificate Authority, $SUBJECT_BASE". - If creating a self-signed CA, to meet Dogtag's expectations some normalisation may occur. Specifically: the "most specific" CN AVA encountered shall be the most specific RDN (it is moved if necessary); if the subject DN does not contain a CN AVA, then "CN=Certificate Authority" is appended. - The subject base need not be related to or contained within the CA subject DN. - During ipa-server-install print a summary of the subject base and CA subject DN that will be used, including a note if the subject DN was normalised. Fixes: https://fedorahosted.org/freeipa/ticket/2614 --- install/share/certmap.conf.template | 2 +- install/tools/ipa-ca-install | 27 ++++++++----- install/tools/man/ipa-server-install.1 | 12 +++++- ipaserver/install/ca.py | 21 +++++----- ipaserver/install/cainstance.py | 36 +++++++++-------- ipaserver/install/certs.py | 20 +++++++--- ipaserver/install/dsinstance.py | 57 +++++++++++--------------- ipaserver/install/installutils.py | 39 ++++++++++++++++-- ipaserver/install/ipa_cacert_manage.py | 9 ++++- ipaserver/install/krainstance.py | 22 +++++----- ipaserver/install/server/common.py | 46 +++++++++++++-------- ipaserver/install/server/install.py | 64 ++++++++++++++++++++++++------ ipaserver/install/server/replicainstall.py | 22 +++++++--- 13 files changed, 251 insertions(+), 126 deletions(-) diff --git a/install/share/certmap.conf.template b/install/share/certmap.conf.template index e76bf3c653a4f1d130ce8c264a28cac5dc63925c..d59b095faff804eae4cbd2ef984aa8ca3be52946 100644 --- a/install/share/certmap.conf.template +++ b/install/share/certmap.conf.template @@ -41,6 +41,6 @@ certmap default default #default:InitFn <Init function's name> default:DNComps default:FilterComps uid -certmap ipaca CN=Certificate Authority,$SUBJECT_BASE +certmap ipaca $ISSUER_DN ipaca:CmapLdapAttr seeAlso ipaca:verifycert on diff --git a/install/tools/ipa-ca-install b/install/tools/ipa-ca-install index 985e7413aa06900976934c329757ce45da5ff12d..a506984924fdd56b552aaf78ba5a5f0e6b10e461 100755 --- a/install/tools/ipa-ca-install +++ b/install/tools/ipa-ca-install @@ -20,6 +20,7 @@ import sys import os +import six import shutil import tempfile from ipapython import ipautil @@ -31,9 +32,10 @@ from ipaserver.install.installutils import check_creds, ReplicaConfig from ipaserver.install import bindinstance, dsinstance, ca from ipaserver.install import cainstance, custodiainstance, service from ipapython import version -from ipalib import api -from ipalib.constants import DOMAIN_LEVEL_0 +from ipalib import api, x509 +from ipalib.constants import DOMAIN_LEVEL_0, IPA_CA_CN from ipapython.dn import DN +from ipapython.certdb import get_ca_nickname from ipapython.config import IPAOptionParser from ipapython.ipa_log_manager import root_logger, standard_logging_setup from ipaplatform.paths import paths @@ -41,6 +43,10 @@ from ipaplatform.paths import paths log_file_name = paths.IPAREPLICA_CA_INSTALL_LOG REPLICA_INFO_TOP_DIR = None +if six.PY3: + unicode = str + + def parse_options(): usage = "%prog [options] REPLICA_FILE" parser = IPAOptionParser(usage=usage, version=version.VERSION) @@ -160,9 +166,12 @@ def install_replica(safe_options, options, filename): conn.connect(bind_dn=DN(('cn', 'Directory Manager')), bind_pw=dirman_password) - if config.subject_base is None: - attrs = conn.get_ipa_config() - config.subject_base = attrs.get('ipacertificatesubjectbase')[0] + # look up CA subject name (needed for DS certmap.conf) + ipa_ca_nickname = get_ca_nickname(config.realm_name) + db = certs.CertDB(config.realm_name, nssdir=paths.IPA_NSSDB_DIR) + cert = db.get_cert_from_db(ipa_ca_nickname) + options.subject_base = dsinstance.DsInstance().find_subject_base() + options.ca_subject = unicode(x509.load_certificate(cert).subject) if config.master_host_name is None: config.ca_host_name = \ @@ -175,7 +184,6 @@ def install_replica(safe_options, options, filename): options.domain_name = config.domain_name options.dm_password = config.dirman_password options.host_name = config.host_name - options.subject = config.subject_base if os.path.exists(cafile): options.ca_cert_file = cafile else: @@ -193,7 +201,8 @@ def install_replica(safe_options, options, filename): host_name=config.host_name, dm_password=config.dirman_password) CA.configure_replica(config.ca_host_name, - subject_base=config.subject_base, + subject_base=options.subject_base, + subject=options.ca_subject, ca_cert_bundle=ca_data) # Install CA DNS records if bindinstance.dns_container_exists(api.env.host, api.env.basedn, @@ -220,13 +229,13 @@ def install_master(safe_options, options): bind_pw=dm_password) config = api.Command['config_show']()['result'] - subject_base = config['ipacertificatesubjectbase'][0] + subject = api.Command.ca_show(IPA_CA_CN)['result']['ipacasubjectdn'] options.realm_name = api.env.realm options.domain_name = api.env.domain options.dm_password = dm_password options.host_name = api.env.host - options.subject = subject_base + options.ca_subject = subject ca.install_check(True, None, options) ca.install(True, None, options) diff --git a/install/tools/man/ipa-server-install.1 b/install/tools/man/ipa-server-install.1 index 55b49449e3c44aebfeefe5cb71d73e9abf07c5b2..9412eab4a345b1f25bdf91a09b4493ba3f27de34 100644 --- a/install/tools/man/ipa-server-install.1 +++ b/install/tools/man/ipa-server-install.1 @@ -129,8 +129,12 @@ Name of the Kerberos KDC SSL certificate to install \fB\-\-ca\-cert\-file\fR=\fIFILE\fR File containing the CA certificate of the CA which issued the Directory Server, Apache Server and Kerberos KDC certificates. The file is accepted in PEM and DER certificate and PKCS#7 certificate chain formats. This option may be used multiple times. Use this option if the CA certificate is not present in the certificate files. .TP -\fB\-\-subject\fR=\fISUBJECT\fR -The certificate subject base (default O=REALM.NAME) +\fB\-\-subject\-base\fR=\fIDN\fR +The certificate subject base (default: O=REALM.NAME). Used in the Subject DN +for host and service certificates issued using the default profile. +.TP +\fB\-\-ca\-subject\fR=\fIDN\fR +The CA certificate subject DN (default: CN=Certificate Authority,SUBJECT_BASE). .TP \fB\-\-ca\-signing\-algorithm\fR=\fIALGORITHM\fR Signing algorithm of the IPA CA certificate. Possible values are SHA1withRSA, SHA256withRSA, SHA512withRSA. Default value is SHA256withRSA. Use this option with --external-ca if the external CA does not support the default signing algorithm. @@ -200,6 +204,10 @@ An unattended uninstallation that will never prompt for user input .TP \fB\-P\fR \fIMASTER_PASSWORD\fR, \fB\-\-master\-password\fR=\fIMASTER_PASSWORD\fR The kerberos master password (normally autogenerated). +.TP +\fB\-\-subject\fR=\fIDN\fR +The certificate subject base (default: O=REALM.NAME). Used in the Subject DN +for host and service certificates issued using the default profile. .SH "EXIT STATUS" 0 if the (un)installation was successful diff --git a/ipaserver/install/ca.py b/ipaserver/install/ca.py index 00e0b038ca03320fd7b8268fb3eb96c5bc50a3ac..e143944d2af11dabd2e870484c7c2a1178a1f885 100644 --- a/ipaserver/install/ca.py +++ b/ipaserver/install/ca.py @@ -25,7 +25,7 @@ def install_check(standalone, replica_config, options): realm_name = options.realm_name host_name = options.host_name - subject_base = options.subject + subject_base = options.subject_base if replica_config is not None: if standalone and api.env.ra_plugin == 'selfsign': @@ -68,7 +68,7 @@ def install_check(standalone, replica_config, options): "--external-ca.") external_cert_file, external_ca_file = installutils.load_external_cert( - options.external_cert_files, options.subject) + options.external_cert_files, options.ca_subject) elif options.external_ca: if cainstance.is_step_one_done(): raise ScriptError( @@ -104,7 +104,7 @@ def install_check(standalone, replica_config, options): if not cert: continue subject = DN(str(x509.get_subject(cert))) - if subject in (DN('CN=Certificate Authority', subject_base), + if subject in (DN(options.ca_subject), DN('CN=IPA RA', subject_base), DN('CN=Object Signing Cert', subject_base)): raise ScriptError( @@ -122,7 +122,6 @@ def install_step_0(standalone, replica_config, options): domain_name = options.domain_name dm_password = options.dm_password host_name = options.host_name - subject_base = options.subject if replica_config is not None: # Configure the CA if necessary @@ -134,8 +133,10 @@ def install_step_0(standalone, replica_config, options): if standalone: api.Backend.ldap2.disconnect() - cainstance.install_replica_ca(replica_config, postinstall, - ra_p12=getattr(options, 'ra_p12', None)) + cainstance.install_replica_ca( + replica_config, postinstall, + ra_p12=getattr(options, 'ra_p12', None), + subject=options.ca_subject) if standalone and not api.Backend.ldap2.isconnected(): api.Backend.ldap2.connect(bind_dn=DN(('cn', 'Directory Manager')), @@ -155,19 +156,19 @@ def install_step_0(standalone, replica_config, options): ca.create_ra_agent_db = False if external == 0: ca.configure_instance(host_name, dm_password, - dm_password, subject_base=subject_base, + dm_password, subject=options.ca_subject, ca_signing_algorithm=options.ca_signing_algorithm) elif external == 1: ca.configure_instance(host_name, dm_password, dm_password, csr_file=paths.ROOT_IPA_CSR, - subject_base=subject_base, + subject=options.ca_subject, ca_signing_algorithm=options.ca_signing_algorithm, ca_type=options.external_ca_type) else: ca.configure_instance(host_name, dm_password, dm_password, cert_file=external_cert_file.name, cert_chain_file=external_ca_file.name, - subject_base=subject_base, + subject=options.ca_subject, ca_signing_algorithm=options.ca_signing_algorithm) @@ -176,7 +177,7 @@ def install_step_1(standalone, replica_config, options): domain_name = options.domain_name dm_password = options.dm_password host_name = options.host_name - subject_base = options.subject + subject_base = options.subject_base basedn = ipautil.realm_to_suffix(realm_name) diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py index 2ec02d6628ebc9e3a9bad141ec636c84eab14cef..94898afcd0aa9b3e2326089cafd9f2f9723202e9 100644 --- a/ipaserver/install/cainstance.py +++ b/ipaserver/install/cainstance.py @@ -345,7 +345,8 @@ class CAInstance(DogtagInstance): pkcs12_info=None, master_host=None, csr_file=None, cert_file=None, cert_chain_file=None, master_replication_port=None, - subject_base=None, ca_signing_algorithm=None, + subject_base=None, subject=None, + ca_signing_algorithm=None, ca_type=None, ra_p12=None): """Create a CA instance. @@ -365,10 +366,12 @@ class CAInstance(DogtagInstance): self.clone = True self.master_host = master_host self.master_replication_port = master_replication_port - if subject_base is None: - self.subject_base = DN(('O', self.realm)) - else: - self.subject_base = subject_base + + self.subject_base = \ + subject_base or installutils.default_subject_base(self.realm) + self.subject = \ + subject or installutils.default_ca_subject_dn(self.subject_base) + if ca_signing_algorithm is None: self.ca_signing_algorithm = 'SHA256withRSA' else: @@ -502,7 +505,7 @@ class CAInstance(DogtagInstance): config.set("CA", "pki_audit_signing_subject_dn", str(DN(('cn', 'CA Audit'), self.subject_base))) config.set("CA", "pki_ca_signing_subject_dn", - str(DN(('cn', 'Certificate Authority'), self.subject_base))) + str(self.subject)) # Certificate nicknames config.set("CA", "pki_subsystem_nickname", "subsystemCert cert-pki-ca") @@ -776,7 +779,7 @@ class CAInstance(DogtagInstance): userCertificate=[cert_data], description=['2;%s;%s;%s' % ( cert.serial_number, - DN(('CN', 'Certificate Authority'), self.subject_base), + DN(self.subject), DN(('CN', 'IPA RA'), self.subject_base))]) conn.add_entry(entry) @@ -855,7 +858,7 @@ class CAInstance(DogtagInstance): st = 1 en = 0 subid = 0 - ca_dn = DN(('CN','Certificate Authority'), self.subject_base) + ca_dn = DN(self.subject) while st > 0: st = certlist.find('-----BEGIN', en) en = certlist.find('-----END', en+1) @@ -1306,7 +1309,7 @@ class CAInstance(DogtagInstance): basedn = ipautil.realm_to_suffix(self.realm) self.ldap_enable('CA', self.fqdn, None, basedn) - def configure_replica(self, master_host, subject_base=None, + def configure_replica(self, master_host, subject_base=None, subject=None, ca_cert_bundle=None, ca_signing_algorithm=None, ca_type=None): """Creates a replica CA, creating a local DS backend and using @@ -1315,10 +1318,12 @@ class CAInstance(DogtagInstance): """ self.master_host = master_host self.master_replication_port = 389 - if subject_base is None: - self.subject_base = DN(('O', self.realm)) - else: - self.subject_base = subject_base + + self.subject_base = \ + subject_base or installutils.default_subject_base(self.realm) + self.subject = \ + subject or installutils.default_ca_subject_dn(self.subject_base) + if ca_signing_algorithm is None: self.ca_signing_algorithm = 'SHA256withRSA' else: @@ -1490,7 +1495,7 @@ def replica_ca_install_check(config): exit('IPA schema missing on master CA directory server') -def install_replica_ca(config, postinstall=False, ra_p12=None): +def install_replica_ca(config, postinstall=False, ra_p12=None, subject=None): """ Install a CA on a replica. @@ -1510,7 +1515,6 @@ def install_replica_ca(config, postinstall=False, ra_p12=None): ca = CAInstance(config.realm_name, certs.NSS_DIR) ca.dm_password = config.dirman_password - ca.subject_base = config.subject_base if not config.setup_ca: # We aren't configuring the CA in this step but we still need @@ -1529,7 +1533,7 @@ def install_replica_ca(config, postinstall=False, ra_p12=None): pkcs12_info=(cafile,), ra_p12=ra_p12, master_host=config.master_host_name, master_replication_port=config.ca_ds_port, - subject_base=config.subject_base) + subject=subject) # Restart httpd since we changed it's config and added ipa-pki-proxy.conf # Without the restart, CA service status check would fail due to missing diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py index 9eaec73300ebb61f2e71e72a03114c870966d95f..b154c3b9e36ea874b45225b0ba09af468c115072 100644 --- a/ipaserver/install/certs.py +++ b/ipaserver/install/certs.py @@ -72,9 +72,19 @@ class CertDB(object): This class knows IPA-specific details such as nssdir location, or the CA cert name. + + ``ca_subject_dn`` + IPA CA subject DN. This argument is required when importing + CA certificates into the certificate database. + ``subject_base`` + Realm subject base DN. This argument is required when creating + server or object signing certs. + """ # TODO: Remove all selfsign code - def __init__(self, realm, nssdir=NSS_DIR, fstore=None, host_name=None, subject_base=None): + def __init__( + self, realm, nssdir=NSS_DIR, fstore=None, host_name=None, + ca_subject_dn=None, subject_base=None): self.nssdb = NSSDatabase(nssdir) self.secdir = nssdir @@ -93,15 +103,13 @@ class CertDB(object): self.certreq_fname = None self.certder_fname = None self.host_name = host_name + self.ca_subject_dn = ca_subject_dn self.subject_base = subject_base try: self.cwd = os.getcwd() except OSError as e: raise RuntimeError("Unable to determine the current directory: %s" % str(e)) - if not subject_base: - self.subject_base = DN(('O', 'IPA')) - self.cacert_name = get_ca_nickname(self.realm) self.valid_months = "120" self.keysize = "1024" @@ -120,6 +128,7 @@ class CertDB(object): else: self.fstore = sysrestore.FileStore(paths.SYSRESTORE) + ca_subject_dn = ipautil.dn_attribute_property('_ca_subject_dn') subject_base = ipautil.dn_attribute_property('_subject_base') def __del__(self): @@ -253,13 +262,12 @@ class CertDB(object): certs = fd.read() fd.close() - ca_dn = DN(('CN','Certificate Authority'), self.subject_base) st = 0 while True: try: (cert, st) = find_cert_from_txt(certs, st) (rdn, subject_dn) = get_cert_nickname(cert) - if subject_dn == ca_dn: + if subject_dn == self.ca_subject_dn: nick = get_ca_nickname(self.realm) else: nick = str(subject_dn) diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py index 26cd2461a6833b7e129411b0005ba3a521bd3232..525857c3b5eef60dd1639a465fd2988646329904 100644 --- a/ipaserver/install/dsinstance.py +++ b/ipaserver/install/dsinstance.py @@ -238,6 +238,7 @@ class DsInstance(service.Service): self.dercert = None self.idstart = None self.idmax = None + self.subject = None self.subject_base = None self.open_ports = [] self.run_init_memberof = True @@ -255,6 +256,7 @@ class DsInstance(service.Service): self.fstore = sysrestore.FileStore(paths.SYSRESTORE) + subject = ipautil.dn_attribute_property('_subject') subject_base = ipautil.dn_attribute_property('_subject_base') def __common_setup(self, enable_ssl=False): @@ -303,7 +305,8 @@ class DsInstance(service.Service): self.step("configuring directory to start on boot", self.__enable) def init_info(self, realm_name, fqdn, domain_name, dm_password, - subject_base, idstart, idmax, pkcs12_info, ca_file=None): + subject_base, subject, + idstart, idmax, pkcs12_info, ca_file=None): self.realm = realm_name.upper() self.serverid = installutils.realm_to_serverid(self.realm) self.suffix = ipautil.realm_to_suffix(self.realm) @@ -312,6 +315,7 @@ class DsInstance(service.Service): self.domain = domain_name self.principal = "ldap/%s@%s" % (self.fqdn, self.realm) self.subject_base = subject_base + self.subject = subject self.idstart = idstart self.idmax = idmax self.pkcs12_info = pkcs12_info @@ -323,11 +327,13 @@ class DsInstance(service.Service): def create_instance(self, realm_name, fqdn, domain_name, dm_password, pkcs12_info=None, - idstart=1100, idmax=999999, subject_base=None, + idstart=1100, idmax=999999, + subject_base=None, subject=None, hbac_allow=True, ca_file=None): self.init_info( realm_name, fqdn, domain_name, dm_password, - subject_base, idstart, idmax, pkcs12_info, ca_file=ca_file) + subject_base, subject, + idstart, idmax, pkcs12_info, ca_file=ca_file) self.__common_setup() self.step("restarting directory server", self.__restart_instance) @@ -361,8 +367,9 @@ class DsInstance(service.Service): self.start_creation(runtime=10) def create_replica(self, realm_name, master_fqdn, fqdn, - domain_name, dm_password, subject_base, api, - pkcs12_info=None, ca_file=None, + domain_name, dm_password, + subject_base, subject, + api, pkcs12_info=None, ca_file=None, ca_is_configured=None, promote=False): # idstart and idmax are configured so that the range is seen as # depleted by the DNA plugin and the replica will go and get a @@ -377,6 +384,7 @@ class DsInstance(service.Service): domain_name=domain_name, dm_password=dm_password, subject_base=subject_base, + subject=subject, idstart=idstart, idmax=idmax, pkcs12_info=pkcs12_info, @@ -760,7 +768,9 @@ class DsInstance(service.Service): def __enable_ssl(self): dirname = config_dirname(self.serverid) - dsdb = certs.CertDB(self.realm, nssdir=dirname, subject_base=self.subject_base) + dsdb = certs.CertDB( + self.realm, nssdir=dirname, + ca_subject_dn=self.subject, subject_base=self.subject_base) if self.pkcs12_info: if self.ca_is_configured: trust_flags = 'CT,C,C' @@ -878,7 +888,7 @@ class DsInstance(service.Service): shutil.copyfile(ipautil.SHARE_DIR + "certmap.conf.template", config_dirname(self.serverid) + "certmap.conf") installutils.update_file(config_dirname(self.serverid) + "certmap.conf", - '$SUBJECT_BASE', str(self.subject_base)) + '$ISSUER_DN', str(self.subject)) sysupgrade.set_upgrade_state( 'certmap.conf', 'subject_base', @@ -1021,7 +1031,9 @@ class DsInstance(service.Service): self.stop() dirname = config_dirname(installutils.realm_to_serverid(self.realm)) - certdb = certs.CertDB(self.realm, nssdir=dirname, subject_base=self.subject_base) + certdb = certs.CertDB( + self.realm, nssdir=dirname, + ca_subject_dn=self.subject, subject_base=self.subject_base) if not cacert_name or len(cacert_name) == 0: cacert_name = "Imported CA" # we can't pass in the nickname, so we set the instance variable @@ -1144,8 +1156,7 @@ class DsInstance(service.Service): Try to find the current value of certificate subject base. 1) Look in sysupgrade first 2) If no value is found there, look in DS (start DS if necessary) - 3) Last resort, look in the certmap.conf itself - 4) If all fails, log loudly and return None + 3) If all fails, log loudly and return None Note that this method can only be executed AFTER the ipa server is configured, the api is initialized elsewhere and @@ -1193,27 +1204,6 @@ class DsInstance(service.Service): except Exception: pass - if not subject_base: - root_logger.debug('Unable to find certificate subject base in DS') - root_logger.debug('Trying to find certificate subject base in ' - 'certmap.conf') - - certmap_dir = config_dirname( - installutils.realm_to_serverid(api.env.realm) - ) - try: - with open(os.path.join(certmap_dir, 'certmap.conf')) as f: - for line in f: - if line.startswith('certmap ipaca'): - subject_base = line.strip().split(',')[-1] - root_logger.debug( - 'Found certificate subject base in certmap.conf: ' - '%s', subject_base) - - except IOError as e: - root_logger.error('Cannot open certmap.conf to find certificate ' - 'subject base: %s', e.strerror) - if subject_base: return subject_base @@ -1250,9 +1240,10 @@ class DsInstance(service.Service): os.chown(paths.DS_KEYTAB, pent.pw_uid, pent.pw_gid) def __get_ds_cert(self): - subject = DN(('O', self.realm)) nssdb_dir = config_dirname(self.serverid) - db = certs.CertDB(self.realm, nssdir=nssdb_dir, subject_base=subject) + db = certs.CertDB( + self.realm, nssdir=nssdb_dir, + ca_subject_dn=self.subject, subject_base=self.subject_base) db.request_service_cert(self.nickname, self.principal, self.fqdn) db.create_pin_file() diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py index 7578bf8f5c0ed358014e39914ce37b65a778ea48..7144231faf40e5f658bc70fb5b1b417bea479a45 100644 --- a/ipaserver/install/installutils.py +++ b/ipaserver/install/installutils.py @@ -996,7 +996,7 @@ def check_entropy(): except ValueError as e: root_logger.debug("Invalid value in %s %s", paths.ENTROPY_AVAIL, e) -def load_external_cert(files, subject_base): +def load_external_cert(files, subject): """ Load and verify external CA certificate chain from multiple files. @@ -1004,7 +1004,7 @@ def load_external_cert(files, subject_base): chain formats. :param files: Names of files to import - :param subject_base: Subject name base for IPA certificates + :param subject: IPA CA subject DN :returns: Temporary file with the IPA CA certificate and temporary file with the external CA certificate chain """ @@ -1018,7 +1018,7 @@ def load_external_cert(files, subject_base): except RuntimeError as e: raise ScriptError(str(e)) - ca_subject = DN(('CN', 'Certificate Authority'), subject_base) + ca_subject = DN(subject) ca_nickname = None cache = {} for nickname, trust_flags in nssdb.list_certs(): @@ -1377,3 +1377,36 @@ def remove_ccache(ccache_path=None, run_as=None): except ipautil.CalledProcessError as e: root_logger.warning( "Failed to clear Kerberos credentials cache: {}".format(e)) + + +def default_subject_base(realm_name): + return DN(('O', realm_name)) + + +def default_ca_subject_dn(subject_base): + return DN(('CN', 'Certificate Authority'), subject_base) + + +def normalize_dogtag_ca_subject_dn(dn): + """ + Prepare a CA subject DN to be compliant with Dogtag. + + Move the most specific CN AVA encountered to be the most + specific RDN, if it is not already so. + + If no CN AVA is encountered, add 'CN=Certificate Authority' as + the most specific RDN. + + """ + cn_ava = None + l = [] + for rdn in DN(dn): + for ava in rdn: + if cn_ava is None and ava.attr.lower() == 'cn': + cn_ava = ava + else: + l.append(ava) + if cn_ava is None: + cn_ava = ('cn', 'Certificate Authority') + l.insert(0, cn_ava) + return DN(*l) diff --git a/ipaserver/install/ipa_cacert_manage.py b/ipaserver/install/ipa_cacert_manage.py index 32ef25c7aac3e57d27955b6a2608adb6a1626019..044328464bfe9ce8fea3ffb03d6fa92eb70fa822 100644 --- a/ipaserver/install/ipa_cacert_manage.py +++ b/ipaserver/install/ipa_cacert_manage.py @@ -24,6 +24,7 @@ from optparse import OptionGroup from nss import nss from nss.error import NSPRError import gssapi +import six from ipapython import admintool, certmonger, ipautil from ipapython.dn import DN @@ -31,6 +32,9 @@ from ipaplatform.paths import paths from ipalib import api, errors, x509, certstore from ipaserver.install import certs, cainstance, installutils +if six.PY3: + unicode = str + class CACertManage(admintool.AdminTool): command_name = 'ipa-cacert-manage' @@ -198,8 +202,6 @@ class CACertManage(admintool.AdminTool): options = self.options conn = api.Backend.ldap2 - cert_file, ca_file = installutils.load_external_cert( - options.external_cert_files, x509.subject_base()) nss_cert = None nss.nss_init(paths.PKI_TOMCAT_ALIAS_DIR) @@ -211,6 +213,9 @@ class CACertManage(admintool.AdminTool): pkinfo = nss_cert.subject_public_key_info.format() #pylint: enable=E1101 + cert_file, ca_file = installutils.load_external_cert( + options.external_cert_files, unicode(subject)) + nss_cert = x509.load_certificate_from_file(cert_file.name) cert = nss_cert.der_data if nss_cert.subject != subject: diff --git a/ipaserver/install/krainstance.py b/ipaserver/install/krainstance.py index 590a8407d76a1c54dd2323986a8981b7c4851daa..e8de01adfb50fc01159e4a7d3fe5c88ce9465911 100644 --- a/ipaserver/install/krainstance.py +++ b/ipaserver/install/krainstance.py @@ -80,7 +80,7 @@ class KRAInstance(DogtagInstance): def configure_instance(self, realm_name, host_name, dm_password, admin_password, pkcs12_info=None, master_host=None, - subject_base=None): + subject_base=None, subject=None): """Create a KRA instance. To create a clone, pass in pkcs12_info. @@ -92,10 +92,12 @@ class KRAInstance(DogtagInstance): if self.pkcs12_info is not None: self.clone = True self.master_host = master_host - if subject_base is None: - self.subject_base = DN(('O', self.realm)) - else: - self.subject_base = subject_base + + self.subject_base = \ + subject_base or installutils.default_subject_base(realm_name) + self.subject = \ + subject or installutils.default_ca_subject_dn(self.subject_base) + self.realm = realm_name self.suffix = ipautil.realm_to_suffix(realm_name) @@ -296,7 +298,7 @@ class KRAInstance(DogtagInstance): userCertificate=[cert_data], description=['2;%s;%s;%s' % ( cert.serial_number, - DN(('CN', 'Certificate Authority'), self.subject_base), + DN(self.subject), DN(('CN', 'IPA RA'), self.subject_base))]) conn.add_entry(entry) @@ -364,10 +366,10 @@ class KRAInstance(DogtagInstance): self.fqdn = host_name self.dm_password = dm_password self.master_host = master_host - if subject_base is None: - self.subject_base = DN(('O', self.realm)) - else: - self.subject_base = subject_base + + self.subject_base = \ + subject_base or installutils.default_subject_base(self.realm) + self.suffix = ipautil.realm_to_suffix(self.realm) self.pkcs12_info = kra_cert_bundle diff --git a/ipaserver/install/server/common.py b/ipaserver/install/server/common.py index e6093d15cd1067a83ed89945c4a9c983c66ec06f..9f00728dd40083f53ceed5a4c2bb9dc01e087b78 100644 --- a/ipaserver/install/server/common.py +++ b/ipaserver/install/server/common.py @@ -19,7 +19,7 @@ from ipapython.dnsutil import check_zone_overlap if six.PY3: unicode = str -VALID_SUBJECT_ATTRS = ['st', 'o', 'ou', 'dnqualifier', 'c', +VALID_SUBJECT_ATTRS = ['cn', 'st', 'o', 'ou', 'dnqualifier', 'c', 'serialnumber', 'l', 'title', 'sn', 'givenname', 'initials', 'generationqualifier', 'dc', 'mail', 'uid', 'postaladdress', 'postalcode', 'postofficebox', @@ -28,6 +28,21 @@ VALID_SUBJECT_ATTRS = ['st', 'o', 'ou', 'dnqualifier', 'c', 'incorporationcountry', 'businesscategory'] +def _subject_validator(knob, value): + v = unicode(value, 'utf-8') + if any(ord(c) < 0x20 for c in v): + raise ValueError("must not contain control characters") + if '&' in v: + raise ValueError("must not contain an ampersand (\"&\")") + try: + dn = DN(v) + for rdn in dn: + if rdn.attr.lower() not in VALID_SUBJECT_ATTRS: + raise ValueError("invalid attribute: \"%s\"" % rdn.attr) + except ValueError as e: + raise ValueError("invalid subject base format: %s" % e) + + class BaseServerCA(common.Installable, core.Group, core.Composite): description = "certificate system" @@ -130,23 +145,22 @@ class BaseServerCA(common.Installable, core.Group, core.Composite): subject = Knob( str, None, + deprecated=True, description="The certificate subject base (default O=<realm-name>)", - ) + cli_metavar='DN', + ).validator(_subject_validator) - @subject.validator - def subject(self, value): - v = unicode(value, 'utf-8') - if any(ord(c) < 0x20 for c in v): - raise ValueError("must not contain control characters") - if '&' in v: - raise ValueError("must not contain an ampersand (\"&\")") - try: - dn = DN(v) - for rdn in dn: - if rdn.attr.lower() not in VALID_SUBJECT_ATTRS: - raise ValueError("invalid attribute: \"%s\"" % rdn.attr) - except ValueError as e: - raise ValueError("invalid subject base format: %s" % e) + subject_base = Knob( + str, None, + description="The certificate subject base (default O=<realm-name>)", + cli_metavar='DN', + ).validator(_subject_validator) + + ca_subject = Knob( + str, None, + description="The CA certificate subject DN (default CN=Certificate Authority,$SUBJECT_BASE)", + cli_metavar='DN', + ).validator(_subject_validator) ca_signing_algorithm = Knob( {'SHA1withRSA', 'SHA256withRSA', 'SHA512withRSA'}, None, diff --git a/ipaserver/install/server/install.py b/ipaserver/install/server/install.py index 6644a6b31943f6a4826a531df5c50df9a562fdff..b54028437202f8ee78933a9b999e50a7478bd01a 100644 --- a/ipaserver/install/server/install.py +++ b/ipaserver/install/server/install.py @@ -239,7 +239,7 @@ def check_dirsrv(unattended): raise ScriptError(msg) -def set_subject_in_config(realm_name, dm_password, suffix, subject_base): +def set_subject_base_in_config(realm_name, dm_password, suffix, subject_base): ldapuri = 'ldapi://%%2fvar%%2frun%%2fslapd-%s.socket' % ( installutils.realm_to_serverid(realm_name) ) @@ -335,6 +335,13 @@ def install_check(installer): "manually.") print(textwrap.fill(msg, width=79, replace_whitespace=False)) + if options.subject: + msg = ( + "WARNING: option '--subject' is deprecated. " + "It has been superseded by '--subject-base'." + ) + print(textwrap.fill(msg, width=79, replace_whitespace=False)) + installer._installation_cleanup = True print("\nThe log file for this installation can be found in " @@ -482,8 +489,27 @@ def install_check(installer): else: realm_name = options.realm_name.upper() - if not options.subject: - options.subject = DN(('O', realm_name)) + if not options.subject_base: + if options.subject: + # deprecated option was supplied + options.subject_base = options.subject + else: + options.subject_base = \ + installutils.default_subject_base(realm_name) + + if not options.ca_subject: + options.ca_subject = \ + installutils.default_ca_subject_dn(options.subject_base) + + ca_subject_normalized = False + if not (options.external_ca or options.external_cert_files): + # CA will be self-signed by Dogtag; + # normalize DN to Dogtag's expectations + sdn_norm = installutils.normalize_dogtag_ca_subject_dn( + options.ca_subject) + if sdn_norm != options.ca_subject: + ca_subject_normalized = True + options.ca_subject = sdn_norm if options.http_cert_files: if options.http_pin is None: @@ -622,6 +648,15 @@ def install_check(installer): print("Realm name: %s" % realm_name) print() + if options.setup_ca: + print("The CA will be configured with:") + dn_msg = "CA subject DN: %s" % options.ca_subject + if ca_subject_normalized: + dn_msg += " (normalized)" + print(dn_msg) + print("Cert subject base: %s" % options.subject_base) + print() + if options.setup_dns: print("BIND DNS server will be configured to serve IPA domain with:") print("Forwarders: %s" % ( @@ -735,7 +770,8 @@ def install(installer): ds.create_instance(realm_name, host_name, domain_name, dm_password, dirsrv_pkcs12_info, idstart=options.idstart, idmax=options.idmax, - subject_base=options.subject, + subject_base=options.subject_base, + subject=options.ca_subject, hbac_allow=not options.no_hbac_allow) else: ds = dsinstance.DsInstance(fstore=fstore, @@ -745,7 +781,8 @@ def install(installer): ds.create_instance(realm_name, host_name, domain_name, dm_password, idstart=options.idstart, idmax=options.idmax, - subject_base=options.subject, + subject_base=options.subject_base, + subject=options.ca_subject, hbac_allow=not options.no_hbac_allow) ntpinstance.ntp_ldap_enable(host_name, ds.suffix, realm_name) @@ -756,7 +793,7 @@ def install(installer): installer._ds = ds ds.init_info( realm_name, host_name, domain_name, dm_password, - options.subject, 1101, 1100, None) + options.subject_base, options.ca_subject, 1101, 1100, None) if setup_ca: if not options.external_cert_files and options.external_ca: @@ -791,12 +828,12 @@ def install(installer): dm_password, master_password, setup_pkinit=not options.no_pkinit, pkcs12_info=pkinit_pkcs12_info, - subject_base=options.subject) + subject_base=options.subject_base) else: krb.create_instance(realm_name, host_name, domain_name, dm_password, master_password, setup_pkinit=not options.no_pkinit, - subject_base=options.subject) + subject_base=options.subject_base) if setup_ca: ca.install_step_1(False, None, options) @@ -821,13 +858,13 @@ def install(installer): if options.http_cert_files: http.create_instance( realm_name, host_name, domain_name, dm_password, - pkcs12_info=http_pkcs12_info, subject_base=options.subject, + pkcs12_info=http_pkcs12_info, subject_base=options.subject_base, auto_redirect=not options.no_ui_redirect, ca_is_configured=setup_ca) else: http.create_instance( realm_name, host_name, domain_name, dm_password, - subject_base=options.subject, + subject_base=options.subject_base, auto_redirect=not options.no_ui_redirect, ca_is_configured=setup_ca) tasks.restore_context(paths.CACHE_IPA_SESSIONS) @@ -837,8 +874,9 @@ def install(installer): os.chmod(CACERT, 0o644) ca_db.publish_ca_cert(CACERT) - set_subject_in_config(realm_name, dm_password, - ipautil.realm_to_suffix(realm_name), options.subject) + set_subject_base_in_config( + realm_name, dm_password, + ipautil.realm_to_suffix(realm_name), options.subject_base) # Apply any LDAP updates. Needs to be done after the configuration file # is created @@ -1190,6 +1228,8 @@ class ServerCA(BaseServerCA): pkinit_cert_name = Knob(BaseServerCA.pkinit_cert_name) ca_cert_files = Knob(BaseServerCA.ca_cert_files) subject = Knob(BaseServerCA.subject) + subject_base = Knob(BaseServerCA.subject_base) + ca_subject = Knob(BaseServerCA.ca_subject) ca_signing_algorithm = Knob(BaseServerCA.ca_signing_algorithm) skip_schema_check = None diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py index c73600ccad4ededd6f5b17dd5a35479af9093166..fa2ef1808ca95618464f3108bce9738ce6d899fd 100644 --- a/ipaserver/install/server/replicainstall.py +++ b/ipaserver/install/server/replicainstall.py @@ -18,6 +18,7 @@ import tempfile import six from ipapython import ipaldap, ipautil, sysrestore +from ipapython.certdb import get_ca_nickname from ipapython.dn import DN from ipapython.install.common import step from ipapython.install.core import Knob @@ -71,7 +72,7 @@ def make_pkcs12_info(directory, cert_name, password_name): return None -def install_http_certs(config, fstore, remote_api): +def install_http_certs(config, fstore, remote_api, subject_base): # Obtain keytab for the HTTP service fstore.backup_file(paths.IPA_KEYTAB) @@ -89,8 +90,7 @@ def install_http_certs(config, fstore, remote_api): # Obtain certificate for the HTTP service nssdir = certs.NSS_DIR - subject = DN(('O', config.realm_name)) - db = certs.CertDB(config.realm_name, nssdir=nssdir, subject_base=subject) + db = certs.CertDB(config.realm_name, nssdir=nssdir, subject_base=subject_base) db.request_service_cert('Server-Cert', principal, config.host_name, True) # FIXME: need Signing-Cert too ? @@ -120,6 +120,7 @@ def install_replica_ds(config, options, ca_is_configured, remote_api, domain_name=config.domain_name, dm_password=config.dirman_password, subject_base=config.subject_base, + subject=options.ca_subject, pkcs12_info=pkcs12_info, ca_is_configured=ca_is_configured, ca_file=ca_file, @@ -599,6 +600,9 @@ def install_check(installer): raise RuntimeError("CA cert file is not available. Please run " "ipa-replica-prepare to create a new replica file.") + # look up CA subject name (needed for DS certmap.conf) + options.ca_subject = unicode(x509.load_certificate_from_file(cafile).subject) + for pkcs12_name, pin_name in (('dscert.p12', 'dirsrv_pin.txt'), ('httpcert.p12', 'http_pin.txt')): pkcs12_info = make_pkcs12_info(config.dir, pkcs12_name, pin_name) @@ -717,7 +721,6 @@ def install_check(installer): if options.setup_ca: options.realm_name = config.realm_name options.host_name = config.host_name - options.subject = config.subject_base ca.install_check(False, config, options) if config.setup_kra: @@ -1256,6 +1259,12 @@ def promote_check(installer): if subject_base is not None: config.subject_base = DN(subject_base) + # look up CA subject name (needed for DS certmap.conf) + ipa_ca_nickname = get_ca_nickname(config.realm_name) + db = certs.CertDB(config.realm_name, nssdir=paths.IPA_NSSDB_DIR) + cert = db.get_cert_from_db(ipa_ca_nickname) + options.ca_subject = unicode(x509.load_certificate(cert).subject) + # Find if any server has a CA ca_host = service.find_providing_server('CA', conn, api.env.server) if ca_host is not None: @@ -1289,7 +1298,7 @@ def promote_check(installer): options.realm_name = config.realm_name options.host_name = config.host_name - options.subject = config.subject_base + ca.install_check(False, None, options) if config.setup_kra: @@ -1418,7 +1427,7 @@ def promote(installer): # Must install http certs before changing ipa configuration file # or certmonger will fail to contact the peer master - install_http_certs(config, fstore, remote_api) + install_http_certs(config, fstore, remote_api, config.subject_base) ntpinstance.ntp_ldap_enable(config.host_name, ds.suffix, remote_api.env.realm) @@ -1507,6 +1516,7 @@ def promote(installer): dm_password=config.dirman_password) ca.configure_replica(config.ca_host_name, subject_base=config.subject_base, + subject=options.ca_subject, ca_cert_bundle=ca_data) if options.setup_kra: -- 2.5.5
-- Manage your subscription for the Freeipa-devel mailing list: https://www.redhat.com/mailman/listinfo/freeipa-devel Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code