From d27e77adc56f5a04f3bdd1aaed5440a89ed3acad Mon Sep 17 00:00:00 2001 From: Jan Cholasta Date: Mon, 16 Jun 2014 10:41:57 +0200 Subject: Allow upgrading CA-less to CA-full using ipa-ca-install. Part of https://fedorahosted.org/freeipa/ticket/3737 Reviewed-By: Rob Crittenden --- install/tools/ipa-ca-install | 231 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 217 insertions(+), 14 deletions(-) (limited to 'install/tools/ipa-ca-install') diff --git a/install/tools/ipa-ca-install b/install/tools/ipa-ca-install index 3c9307edf..fc8941248 100755 --- a/install/tools/ipa-ca-install +++ b/install/tools/ipa-ca-install @@ -22,6 +22,7 @@ import sys import socket import os, shutil +from ConfigParser import RawConfigParser from ipapython import ipautil @@ -29,15 +30,17 @@ from ipaserver.install import installutils, service from ipaserver.install import certs from ipaserver.install.installutils import (HostnameLocalhost, ReplicaConfig, expand_replica_info, read_replica_info, get_host_name, BadHostError, - private_ccache, read_replica_info_dogtag_port) + private_ccache, read_replica_info_dogtag_port, validate_external_cert) from ipaserver.install import dsinstance, cainstance, bindinstance from ipaserver.install.replication import replica_conn_check from ipapython import version -from ipalib import api, util +from ipalib import api, util, certstore, x509 +from ipalib.constants import CACERT from ipapython.dn import DN from ipapython.config import IPAOptionParser from ipapython import sysrestore from ipapython import dogtag +from ipapython import certdb from ipapython.ipa_log_manager import * from ipaplatform import services from ipaplatform.paths import paths @@ -63,14 +66,34 @@ def parse_options(): default=False, help="skip check for updated CA DS schema on the remote master") parser.add_option("-U", "--unattended", dest="unattended", action="store_true", default=False, help="unattended installation never prompts the user") + parser.add_option("--external-ca", dest="external_ca", action="store_true", + default=False, help="Generate a CSR to be signed by an external CA") + parser.add_option("--external_cert_file", dest="external_cert_file", + help="PEM file containing a certificate signed by the external CA") + parser.add_option("--external_ca_file", dest="external_ca_file", + help="PEM file containing the external CA chain") options, args = parser.parse_args() safe_options = parser.get_safe_opts(options) - if len(args) != 1: - parser.error("you must provide a file generated by ipa-replica-prepare") + if args: + filename = args[0] - return safe_options, options, args[0] + if len(args) != 1: + parser.error("you must provide a file generated by " + "ipa-replica-prepare") + else: + filename = None + + if options.external_ca: + if options.external_cert_file: + parser.error("You cannot specify --external_cert_file " + "together with --external-ca") + if options.external_ca_file: + parser.error("You cannot specify --external_ca_file together " + "with --external-ca") + + return safe_options, options, filename def get_dirman_password(): return installutils.read_password("Directory Manager (existing master)", confirm=False, validate=False) @@ -83,20 +106,18 @@ def install_dns_records(config, options): return bind = bindinstance.BindInstance(dm_password=config.dirman_password) + disconnect = False try: - api.Backend.ldap2.connect(bind_dn=DN(('cn', 'Directory Manager')), - bind_pw=config.dirman_password) + if not api.Backend.ldap2.isconnected(): + api.Backend.ldap2.connect(bind_dn=DN(('cn', 'Directory Manager')), + bind_pw=config.dirman_password) + disconnect = True bind.add_ipa_ca_dns_records(config.host_name, config.domain_name) finally: - if api.Backend.ldap2.isconnected(): + if api.Backend.ldap2.isconnected() and disconnect: api.Backend.ldap2.disconnect() -def main(): - safe_options, options, filename = parse_options() - - if os.geteuid() != 0: - sys.exit("\nYou must be root to run this script.\n") - +def install_replica(safe_options, options, filename): standard_logging_setup(log_file_name, debug=options.debug) root_logger.debug('%s was invoked with argument "%s" and options: %s' % (sys.argv[0], filename, safe_options)) @@ -204,6 +225,188 @@ def main(): root_logger.error(str(e)) sys.exit(1) +def install_master(safe_options, options): + standard_logging_setup(paths.IPASERVER_CA_INSTALL_LOG, debug=options.debug) + + root_logger.debug( + "%s was invoked with options: %s" % (sys.argv[0], safe_options)) + root_logger.debug("IPA version %s" % version.VENDOR_VERSION) + + global sstore + sstore = sysrestore.StateFile(paths.SYSRESTORE) + + if not dsinstance.DsInstance().is_configured(): + sys.exit("IPA server is not configured on this system.\n") + + api.bootstrap(in_server=True) + api.finalize() + + if api.env.enable_ra: + sys.exit("CA is already installed.\n") + + dm_password = options.password + if not dm_password: + if options.unattended: + sys.exit('Directory Manager password required') + try: + dm_password = get_dirman_password() + except KeyboardInterrupt: + sys.exit(0) + if dm_password is None: + sys.exit("Directory Manager password required") + + api.Backend.ldap2.connect(bind_dn=DN(('cn', 'Directory Manager')), + bind_pw=dm_password) + + config = api.Command['config_show']()['result'] + subject_base = config['ipacertificatesubjectbase'][0] + + if options.external_ca: + if cainstance.is_step_one_done(): + print ("CA is already installed.\nRun the installer with " + "--external-cert-file and --external-ca-file.") + sys.exit(1) + elif options.external_cert_file: + if not cainstance.is_step_one_done(): + print ("CA is not installed yet. To install with an external CA " + "is a two-stage process.\nFirst run the installer with " + "--external-ca.") + sys.exit(1) + + try: + validate_external_cert(options.external_cert_file, + options.external_ca_file, subject_base) + except ValueError, e: + print e + sys.exit(1) + + if options.external_cert_file: + external = 2 + elif options.external_ca: + external = 1 + else: + external = 0 + + realm_name = api.env.realm + domain_name = api.env.domain + host_name = api.env.host + + dirname = dsinstance.config_dirname( + dsinstance.realm_to_serverid(realm_name)) + cadb = certs.CertDB(realm_name, subject_base=subject_base) + dsdb = certs.CertDB(realm_name, nssdir=dirname, subject_base=subject_base) + + for db in (cadb, dsdb): + for nickname, trust_flags in db.list_certs(): + if nickname in (certdb.get_ca_nickname(realm_name), + 'ipaCert', + 'Signing-Cert'): + print ("Certificate with nickname %s is present in %s, " + "cannot continue." % (nickname, db.secdir)) + sys.exit(1) + + cert = db.get_cert_from_db(nickname) + if not cert: + continue + subject = DN(str(x509.get_subject(cert))) + if subject in (DN('CN=Certificate Authority', subject_base), + DN('CN=IPA RA', subject_base), + DN('CN=Object Signing Cert', subject_base)): + print ("Certificate with subject %s is present in %s, " + "cannot continue." % (subject, db.secdir)) + sys.exit(1) + + ca = cainstance.CAInstance(realm_name, certs.NSS_DIR, + dogtag_constants=dogtag.install_constants) + ca.create_ra_agent_db = False + if external == 0: + ca.configure_instance(host_name, domain_name, dm_password, + dm_password, subject_base=subject_base) + elif external == 1: + ca.configure_instance(host_name, domain_name, dm_password, + dm_password, csr_file=paths.ROOT_IPA_CSR, + subject_base=subject_base) + else: + ca.configure_instance(host_name, domain_name, dm_password, + dm_password, + cert_file=options.external_cert_file, + cert_chain_file=options.external_ca_file, + subject_base=subject_base) + + ca.stop(ca.dogtag_constants.PKI_INSTANCE_NAME) + + ca.ldap_enable('CA', host_name, dm_password, + ipautil.realm_to_suffix(realm_name), ['caRenewalMaster']) + + ca.enable_client_auth_to_db() + + # Install CA DNS records + config = ReplicaConfig() + config.realm_name = realm_name + config.domain_name = domain_name + config.host_name = config.master_host_name = host_name + config.dirman_password = dm_password + install_dns_records(config, options) + + # We need to restart apache as we drop a new config file in there + services.knownservices.httpd.restart(capture_output=True) + + # Update config file + parser = RawConfigParser() + parser.read(paths.IPA_DEFAULT_CONF) + parser.set('global', 'enable_ra', 'True') + parser.set('global', 'ra_plugin', 'dogtag') + parser.set('global', 'dogtag_version', + str(ca.dogtag_constants.DOGTAG_VERSION)) + with open(paths.IPA_DEFAULT_CONF, 'w') as f: + parser.write(f) + + # Store the new IPA CA cert chain in DS NSS database and LDAP + cadb = certs.CertDB(realm_name, subject_base=subject_base) + dsdb = certs.CertDB(realm_name, nssdir=dirname, subject_base=subject_base) + trust_flags = dict(reversed(cadb.list_certs())) + trust_chain = cadb.find_root_cert('ipaCert')[:-1] + for nickname in trust_chain[:-1]: + cert = cadb.get_cert_from_db(nickname, pem=False) + dsdb.add_cert(cert, nickname, trust_flags[nickname]) + certstore.put_ca_cert_nss(api.Backend.ldap2, api.env.basedn, + cert, nickname, trust_flags[nickname]) + + nickname = trust_chain[-1] + cert = cadb.get_cert_from_db(nickname, pem=False) + dsdb.add_cert(cert, nickname, trust_flags[nickname]) + certstore.put_ca_cert_nss(api.Backend.ldap2, api.env.basedn, + cert, nickname, trust_flags[nickname], + config_ipa=True, config_compat=True) + + # Restart DS + ds = dsinstance.DsInstance() + ds.init_info(realm_name, host_name, domain_name, dm_password, subject_base, + 1101, 1100, None) + ds.restart(ds.serverid) + + # Store DS CA cert in Dogtag NSS database + dogtagdb = certs.CertDB(realm_name, nssdir=ca.dogtag_constants.ALIAS_DIR) + trust_flags = dict(reversed(dsdb.list_certs())) + server_certs = dsdb.find_server_certs() + trust_chain = dsdb.find_root_cert(server_certs[0][0])[:-1] + nickname = trust_chain[-1] + cert = dsdb.get_cert_from_db(nickname) + dogtagdb.add_cert(cert, nickname, trust_flags[nickname]) + + ca.start(ca.dogtag_constants.PKI_INSTANCE_NAME) + +def main(): + safe_options, options, filename = parse_options() + + if os.geteuid() != 0: + sys.exit("\nYou must be root to run this script.\n") + + if filename is not None: + install_replica(safe_options, options, filename) + else: + install_master(safe_options, options) + fail_message = ''' Your system may be partly configured. Run /usr/sbin/ipa-server-install --uninstall to clean up. -- cgit