#! /usr/bin/python -E # Authors: Simo Sorce # Rob Crittenden # # Copyright (C) 2007-2010 Red Hat # see file 'COPYING' for use and warranty information # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import sys import os import errno import logging import grp import subprocess import signal import shutil import glob import traceback from ConfigParser import RawConfigParser import random import tempfile import nss.error from optparse import OptionGroup from ipapython import version from ipapython.ipautil import user_input from ipaserver.install import certs from ipaserver.install import cainstance from ipaserver.install import dsinstance from ipaserver.install import httpinstance from ipaserver.install import service from ipaserver.install.installutils import * from ipaserver.plugins.ldap2 import ldap2 from ipapython.config import IPAOptionParser from ipalib import api, errors, util from ipapython import certmonger from ipapython import services as ipaservices from ipalib.dn import DN VALID_SUBJECT_ATTRS = ['cn', 'st', 'o', 'ou', 'dnqualifier', 'c', 'serialnumber', 'l', 'title', 'sn', 'givenname', 'initials', 'generationqualifier', 'dc', 'mail', 'uid', 'postaladdress', 'postalcode', 'postofficebox', 'houseidentifier', 'e', 'street', 'pseudonym', 'incorporationlocality', 'incorporationstate', 'incorporationcountry', 'businesscategory'] def subject_callback(option, opt_str, value, parser): """ Make sure the certificate subject base is a valid DN """ name = opt_str.replace('--','') v = unicode(value, 'utf-8') 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, e: raise ValueError('Invalid subject base format: %s' % str(e)) parser.values.subject = str(dn) # may as well normalize it def parse_options(): parser = IPAOptionParser(version=version.VERSION) basic_group = OptionGroup(parser, "basic options") basic_group.add_option("-p", "--ds-password", dest="dm_password", sensitive=True, help="admin password") basic_group.add_option("-d", "--debug", dest="debug", action="store_true", default=False, help="print debugging information") basic_group.add_option("-U", "--unattended", dest="unattended", action="store_true", default=False, help="unattended (un)installation never prompts the user") parser.add_option_group(basic_group) cert_group = OptionGroup(parser, "certificate system options") cert_group.add_option("--subject", action="callback", callback=subject_callback, type="string", help="The certificate subject base (default O=)") parser.add_option_group(cert_group) options, args = parser.parse_args() safe_options = parser.get_safe_opts(options) if options.unattended and not options.dm_password: parser.error("In unattended mode you need to provide at least the DM password (-p)") return safe_options, options def get_dirman_password(): return read_password("Directory Manager (existing master)", confirm=False, validate=False) def main(): safe_options, options = parse_options() if os.getegid() != 0: sys.exit("Must be root to set up server") standard_logging_setup("/var/log/ipaserver-ca-convert.log", options.debug) print "\nThe log file for this installation can be found in /var/log/ipaserver-ca-convert.log" # TODO: check selfsign and fail if dogtag already installed logging.debug('%s was invoked with options: %s' % (sys.argv[0], safe_options)) logging.debug("missing options might be asked for interactively later\n") if not is_ipa_configured(): sys.exit("IPA server is not installed on this system.\n") global fstore fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') global sstore sstore = sysrestore.StateFile('/var/lib/ipa/sysrestore') # Configuration for ipalib, we will bootstrap and finalize later, after # we are sure we have the configuration file ready. cfg = dict( context='installer', in_server=True, debug=options.debug ) api.bootstrap(**cfg) api.finalize() print "==============================================================================" print "This program will convert the FreeIPA Server to use the Dogtag CA." print "" print "To accept the default shown in brackets, press the Enter key." print "" print "" print "WARNING: this will DESTROY and REMOVE your selfsigned CA!" print "This will require you to generate and distribute the new CA certificate and" print "new service certificates to all clients and servers in your FreeIPA domain" print "" if not user_input("Are you sure you want to continue with this procedure?", False): print "" print "Aborting CA convertion operation." sys.exit(1) # Use str() to un-unicodify the strings or later on an ldap operation will # fail due to buggy bindings not understanding unicode strings host_name = str(api.env.host) realm_name = str(api.env.realm) domain_name = str(api.env.domain) # get the directory manager password dm_password = options.dm_password if not options.dm_password: try: dm_password = get_dirman_password() except KeyboardInterrupt: sys.exit(0) # Try out the password try: conn = ldap2(shared_instance=False) conn.connect(bind_dn='cn=directory manager', bind_pw=dm_password) conn.disconnect() except errors.ACIError: sys.exit("\nThe password provided is incorrect for LDAP server %s" % host_name) except errors.LDAPError: sys.exit("\nUnable to connect to LDAP server %s" % host_name) # Update the management framework config file target_fname = '/etc/ipa/default.conf' fd = open(target_fname, "a+") fd.write("ra_plugin=dogtag\n") fd.close() if not options.unattended: print "" print "The following operations may take some minutes to complete." print "Please wait until the prompt is returned." print "" # Mess up with api, as we need to reflect changes made to default.conf # after it has been already finalized object.__setattr__(api.env, 'enable_ra', True) object.__setattr__(api.env, 'ra_plugin', 'dogtag') # Clean up any previous self-signed CA that may exist try: os.rename(certs.CA_SERIALNO, certs.CA_SERIALNO + '.selfsign') except: pass cs = cainstance.CADSInstance(host_name, realm_name, domain_name, dm_password) if not cs.is_configured(): cs.create_instance(realm_name, host_name, domain_name, dm_password, subject_base=options.subject) ca = cainstance.CAInstance(realm_name, certs.NSS_DIR) ca.configure_instance(host_name, dm_password, dm_password, subject_base=options.subject) # Now put the CA cert where other instances exepct it ca.publish_ca_cert("/etc/ipa/ca.crt") ca.publish_ca_cert("/usr/share/ipa/html/ca.crt") ca.ldap_enable('CA', host_name, dm_password, api.env.basedn) # Turn on SSL in the dogtag LDAP instance. This will get restarted # later, we don't need SSL now. cs.create_certdb() cs.enable_ssl() # Add the IPA service for storing the PKI-IPA server certificate. cs.add_simple_service(cs.principal) cs.add_cert_to_service() # TODO Create new DS, HTTPS and (if pkinit is configured) KDC certs serverid = dsinstance.realm_to_serverid(realm_name) dirname = dsinstance.config_dirname(serverid).rstrip('/ ') print "Start certmonger if needed" cmonger = ipaservices.knownservices.certmonger ipaservices.knownservices.messagebus.start() cmonger.start() print "Untrack old certs if any" #LDAP try: (o,e,i) = certmonger.stop_tracking(dirname, nickname="Server-Cert") print (o, e, i) except (ipautil.CalledProcessError, RuntimeError), e: logging.error("certmonger failed to stop tracking certificate: %s" % str(e)) # HTTP try: (o,e,i) = certmonger.stop_tracking(certs.NSS_DIR, nickname="Server-Cert") print (o, e, i) except (ipautil.CalledProcessError, RuntimeError), e: logging.error("certmonger failed to stop tracking certificate: %s" % str(e)) print "Generating new cert for LDAP" dsdb = certs.CertDB(realm_name, nssdir=dirname, subject_base=options.subject) nickname = "Server-Cert" cadb = certs.CertDB(realm_name, host_name=host_name, subject_base=options.subject) cadb.export_ca_cert('ipaCert', False) dsdb.create_from_cacert(cadb.cacert_fname, passwd=None) dercert = dsdb.create_server_cert("Server-Cert", host_name, cadb) dsdb.track_server_cert("Server-Cert", "ldap/%s@%s" % (host_name, realm_name), dsdb.passwd_fname) dsdb.create_pin_file() print "Restarting the directory server" ds = dsinstance.DsInstance(fstore=fstore) ds.restart() # ds.add_cert_to_service() print "Generating new cert for HTTPS" http = httpinstance.HTTPInstance(fstore) http.fqdn = host_name http.realm = realm_name http.domain = domain_name http.dm_password = dm_password http.self_signed_ca = False http.pkcs12_info = None http.subject_base = options.subject http._HTTPInstance__setup_ssl() #pylint: disable=E1101 print "Restarting the web server" http.restart() # http.add_cert_to_service() print "==============================================================================" print "Setup complete" print "" print "Be sure to back up the CA certificate stored in /root/cacert.p12" print "This file is required to create replicas. The password for this" print "file is the Directory Manager password" return 0 try: sys.exit(main()) except SystemExit, e: sys.exit(e) except HostnameLocalhost: print "The hostname resolves to the localhost address (127.0.0.1/::1)" print "Please change your /etc/hosts file so that the hostname" print "resolves to the ip address of your network interface." print "The KDC service does not listen on localhost" print "" print "Please fix your /etc/hosts file and restart the setup program" except Exception, e: message = "Unexpected error - see ipaserver-ca-convert.log for details:\n %s" % str(e) print message message = str(e) for str in traceback.format_tb(sys.exc_info()[2]): message = message + "\n" + str logging.debug(message) sys.exit(1)