#! /usr/bin/python -E # Authors: Karl MacMillan # # Copyright (C) 2007 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 tempfile, shutil, os, pwd from ipapython.ipa_log_manager import * import traceback from ConfigParser import SafeConfigParser import krbV from ipapython import ipautil from ipaserver.install import bindinstance, dsinstance, installutils, certs from ipaserver.install.bindinstance import add_zone, add_reverse_zone, add_fwd_rr, add_ptr_rr, dns_container_exists from ipaserver.install.replication import enable_replication_version_checking from ipaserver.install.installutils import resolve_host, BadHostError, HostLookupError from ipaserver.plugins.ldap2 import ldap2 from ipapython import version from ipapython.config import IPAOptionParser from ipalib import api, errors, util def parse_options(): usage = "%prog [options] FQDN (e.g. replica.example.com)" parser = IPAOptionParser(usage=usage, version=version.VERSION) parser.add_option("--dirsrv_pkcs12", dest="dirsrv_pkcs12", help="install certificate for the directory server") parser.add_option("--http_pkcs12", dest="http_pkcs12", help="install certificate for the http server") parser.add_option("--pkinit_pkcs12", dest="pkinit_pkcs12", help="install certificate for the KDC") parser.add_option("--dirsrv_pin", dest="dirsrv_pin", help="PIN for the Directory Server PKCS#12 file") parser.add_option("--http_pin", dest="http_pin", help="PIN for the Apache Server PKCS#12 file") parser.add_option("--pkinit_pin", dest="pkinit_pin", help="PIN for the KDC pkinit PKCS#12 file") parser.add_option("-p", "--password", dest="password", help="Directory Manager (existing master) password") parser.add_option("--ip-address", dest="ip_address", type="ip", help="Add A and PTR records of the future replica") parser.add_option("--reverse-zone", dest="reverse_zone", help="The reverse DNS zone to use") parser.add_option("--no-reverse", dest="no_reverse", action="store_true", default=False, help="Do not create reverse DNS zone") parser.add_option("--ca", dest="ca_file", default="/root/cacert.p12", help="Location of CA PKCS#12 file, default /root/cacert.p12") parser.add_option("--no-pkinit", dest="setup_pkinit", action="store_false", default=True, help="disables pkinit setup steps") options, args = parser.parse_args() if not options.ip_address: if options.reverse_zone: parser.error("You cannot specify a --reverse-zone option without the --ip-address option") if options.no_reverse: parser.error("You cannot specify a --no-reverse option without the --ip-address option") elif options.reverse_zone and options.no_reverse: parser.error("You cannot specify a --reverse-zone option together with --no-reverse") # If any of the PKCS#12 options are selected, all are required. Create a # list of the options and count it to enforce that all are required without # having a huge set of it blocks. if options.setup_pkinit: pkcs12 = [options.dirsrv_pkcs12, options.dirsrv_pin, options.http_pkcs12, options.http_pin, options.pkinit_pkcs12, options.pkinit_pin] num = 6 else: pkcs12 = [options.dirsrv_pkcs12, options.dirsrv_pin, options.http_pkcs12, options.http_pin] num = 4 cnt = pkcs12.count(None) if cnt > 0 and cnt < num: parser.error("All PKCS#12 options are required if any are used.") if len(args) != 1: parser.error("must provide the fully-qualified name of the replica") return options, args def get_subject_base(host_name, dm_password, suffix): try: conn = ldap2(shared_instance=False, base_dn=suffix) conn.connect(bind_dn='cn=directory manager', bind_pw=dm_password) except errors.ExecutionError, e: root_logger.critical("Could not connect to the Directory Server on %s" % host_name) raise e (dn, entry_attrs) = conn.get_ipa_config() conn.disconnect() return entry_attrs.get('ipacertificatesubjectbase', [None])[0] def check_ipa_configuration(realm_name): config_dir = dsinstance.config_dirname(dsinstance.realm_to_serverid(realm_name)) if not ipautil.dir_exists(config_dir): root_logger.error("could not find directory instance: %s" % config_dir) sys.exit(1) def export_certdb(realm_name, ds_dir, dir, passwd_fname, fname, hostname, subject_base=None, is_kdc=False): """realm is the kerberos realm for the IPA server. ds_dir is the location of the master DS we are creating a replica for. dir is the location of the files for the replica we are creating. passwd_fname is the file containing the PKCS#12 password fname is the filename of the PKCS#12 file for this cert (minus the .p12). hostname is the FQDN of the server we're creating a cert for. The subject is handled by certs.CertDB:create_server_cert() """ if is_kdc: nickname = "KDC-Cert" else: nickname = "Server-Cert" try: self_signed = certs.ipa_self_signed() db = certs.CertDB(realm_name, nssdir=dir, subject_base=subject_base) db.create_passwd_file() ca_db = certs.CertDB(realm_name, host_name=api.env.host, subject_base=subject_base) if is_kdc: ca_db.create_kdc_cert("KDC-Cert", hostname, dir) else: db.create_from_cacert(ca_db.cacert_fname) db.create_server_cert(nickname, hostname, ca_db) except Exception, e: raise e pkcs12_fname = dir + "/" + fname + ".p12" try: if is_kdc: ca_db.export_pem_p12(pkcs12_fname, passwd_fname, nickname, dir + "/kdc.pem") else: db.export_pkcs12(pkcs12_fname, passwd_fname, nickname) except ipautil.CalledProcessError, e: print "error exporting Server certificate: " + str(e) remove_file(pkcs12_fname) remove_file(passwd_fname) remove_file(dir + "/cert8.db") remove_file(dir + "/key3.db") remove_file(dir + "/secmod.db") remove_file(dir + "/noise.txt") if is_kdc: remove_file(dir + "/kdc.pem") if ipautil.file_exists(passwd_fname + ".orig"): remove_file(passwd_fname + ".orig") def export_ra_pkcs12(dir, dm_password): """ dir is the location of the files for the replica we are creating. dm_password is the Directory Manager password If this install is using dogtag/RHCS then export the RA certificate. """ if certs.ipa_self_signed(): return (agent_fd, agent_name) = tempfile.mkstemp() os.write(agent_fd, dm_password) os.close(agent_fd) try: try: db = certs.CertDB(api.env.realm, host_name=api.env.host) if db.has_nickname("ipaCert"): pkcs12_fname = "%s/ra.p12" % dir db.export_pkcs12(pkcs12_fname, agent_name, "ipaCert") except Exception, e: raise e finally: os.remove(agent_name) def save_config(dir, realm_name, host_name, domain_name, dest_host, subject_base): config = SafeConfigParser() config.add_section("realm") config.set("realm", "realm_name", realm_name) config.set("realm", "master_host_name", host_name) config.set("realm", "domain_name", domain_name) config.set("realm", "destination_host", dest_host) config.set("realm", "subject_base", subject_base) fd = open(dir + "/realm_info", "w") config.write(fd) def remove_file(fname, ignore_errors=True): try: os.remove(fname) except OSError, e: if not ignore_errors: raise e def copy_files(realm_name, dir): config_dir = dsinstance.config_dirname(dsinstance.realm_to_serverid(realm_name)) try: shutil.copy("/usr/share/ipa/html/ca.crt", dir + "/ca.crt") if ipautil.file_exists("/usr/share/ipa/html/preferences.html"): shutil.copy("/usr/share/ipa/html/preferences.html", dir + "/preferences.html") shutil.copy("/usr/share/ipa/html/configure.jar", dir + "/configure.jar") if ipautil.file_exists("/var/kerberos/krb5kdc/cacert.pem"): shutil.copy("/var/kerberos/krb5kdc/cacert.pem", dir + "/cacert.pem") except Exception, e: print "error copying files: " + str(e) sys.exit(1) def get_dirman_password(): return installutils.read_password("Directory Manager (existing master)", confirm=False, validate=False) def main(): installutils.check_server_configuration() options, args = parse_options() replica_fqdn = args[0] # Just initialize the environment. This is so the installer can have # access to the plugin environment api.bootstrap(in_server=True) api.finalize() #Automatically disable pkinit w/ dogtag until that is supported #[certs.ipa_self_signed() must be called only after api.finalize()] if not options.pkinit_pkcs12 and not certs.ipa_self_signed(): options.setup_pkinit = False if certs.ipa_self_signed_master() == False: sys.exit('A selfsign CA backend can only prepare on the original master') # get the directory manager password dirman_password = options.password if not options.password: try: dirman_password = get_dirman_password() except KeyboardInterrupt: sys.exit(0) if dirman_password is None: sys.exit("\nDirectory Manager password required") # Try out the password try: conn = ldap2(shared_instance=False) conn.connect(bind_dn='cn=directory manager', bind_pw=dirman_password) conn.disconnect() except errors.ACIError: sys.exit("\nThe password provided is incorrect for LDAP server %s" % api.env.host) except errors.LDAPError: sys.exit("\nUnable to connect to LDAP server %s" % api.env.host) try: installutils.verify_fqdn(replica_fqdn, local_hostname=False) except BadHostError, e: msg = str(e) if isinstance(e, HostLookupError): if options.ip_address is None: if dns_container_exists(api.env.host, api.env.basedn, dm_password=dirman_password, ldapi=True, realm=api.env.realm): msg += '\nAdd the --ip-address argument to create a DNS entry.' sys.exit(msg) else: # The host doesn't exist in DNS but we're adding it. pass else: sys.exit(msg) if options.ip_address: if not dns_container_exists(api.env.host, api.env.basedn, dm_password=dirman_password, ldapi=True, realm=api.env.realm): print "You can't add a DNS record because DNS is not set up." sys.exit(1) if options.reverse_zone and not bindinstance.verify_reverse_zone(options.reverse_zone, options.ip_address): sys.exit(1) if not certs.ipa_self_signed() and not ipautil.file_exists("/var/lib/pki-ca/conf/CS.cfg") and not options.dirsrv_pin: sys.exit("The replica must be created on the primary IPA server.\nIf you installed IPA with your own certificates using PKCS#12 files you must provide PKCS#12 files for any replicas you create as well.") check_ipa_configuration(api.env.realm) if api.env.host == replica_fqdn: print "You can't create a replica on itself" sys.exit(1) ds_dir = dsinstance.config_dirname(dsinstance.realm_to_serverid(api.env.realm)) print "Preparing replica for %s from %s" % (replica_fqdn, api.env.host) enable_replication_version_checking(api.env.host, api.env.realm, dirman_password) subject_base = get_subject_base(api.env.host, dirman_password, util.realm_to_suffix(api.env.realm)) top_dir = tempfile.mkdtemp("ipa") dir = top_dir + "/realm_info" os.mkdir(dir, 0700) if options.dirsrv_pin: passwd = options.dirsrv_pin else: passwd = "" passwd_fname = dir + "/dirsrv_pin.txt" fd = open(passwd_fname, "w") fd.write("%s\n" % passwd) fd.close() if options.dirsrv_pkcs12: print "Copying SSL certificate for the Directory Server from %s" % options.dirsrv_pkcs12 try: shutil.copy(options.dirsrv_pkcs12, dir + "/dscert.p12") except IOError, e: print "Copy failed %s" % e sys.exit(1) else: try: if not certs.ipa_self_signed(): # FIXME, need option for location of CA backup if ipautil.file_exists(options.ca_file): shutil.copy(options.ca_file, dir + "/cacert.p12") else: raise RuntimeError("Root CA PKCS#12 not found in %s" % options.ca_file) except IOError, e: print "Copy failed %s" % e sys.exit(1) print "Creating SSL certificate for the Directory Server" try: export_certdb(api.env.realm, ds_dir, dir, passwd_fname, "dscert", replica_fqdn, subject_base) except errors.CertificateOperationError, e: print "%s" % e sys.exit(1) if not certs.ipa_self_signed(): print "Creating SSL certificate for the dogtag Directory Server" try: export_certdb(api.env.realm, ds_dir, dir, passwd_fname, "dogtagcert", replica_fqdn, subject_base) except errors.CertificateOperationError, e: print "%s" % e sys.exit(1) if options.http_pin: passwd = options.http_pin else: passwd = "" passwd_fname = dir + "/http_pin.txt" fd = open(passwd_fname, "w") fd.write("%s\n" % passwd) fd.close() if options.http_pkcs12: print "Copying SSL certificate for the Web Server from %s" % options.http_pkcs12 try: shutil.copy(options.http_pkcs12, dir + "/httpcert.p12") except IOError, e: print "Copy failed %s" % e sys.exit(1) else: print "Creating SSL certificate for the Web Server" try: export_certdb(api.env.realm, ds_dir, dir, passwd_fname, "httpcert", replica_fqdn, subject_base) except errors.CertificateOperationError, e: print "%s" % e sys.exit(1) print "Exporting RA certificate" export_ra_pkcs12(dir, dirman_password) if options.setup_pkinit: if options.pkinit_pin: passwd = options.pkinit_pin else: passwd = "" passwd_fname = dir + "/pkinit_pin.txt" fd = open(passwd_fname, "w") fd.write("%s\n" % passwd) fd.close() if options.pkinit_pkcs12: print "Copying SSL certificate for the KDC from %s" % options.pkinit_pkcs12 try: shutil.copy(options.pkinit_pkcs12, dir + "/pkinitcert.p12") except IOError, e: print "Copy failed %s" % e sys.exit(1) else: print "Creating SSL certificate for the KDC" try: export_certdb(api.env.realm, ds_dir, dir, passwd_fname, "pkinitcert", replica_fqdn, subject_base, is_kdc=True) except errors.CertificateOperationError, e: print "%s" % e sys.exit(1) print "Copying additional files" copy_files(api.env.realm, dir) print "Finalizing configuration" save_config(dir, api.env.realm, api.env.host, api.env.domain, replica_fqdn, subject_base) replicafile = "/var/lib/ipa/replica-info-" + replica_fqdn encfile = replicafile+".gpg" print "Packaging replica information into %s" % encfile ipautil.run(["/bin/tar", "cf", replicafile, "-C", top_dir, "realm_info"]) ipautil.encrypt_file(replicafile, encfile, dirman_password, top_dir); remove_file(replicafile) shutil.rmtree(dir) if options.ip_address: print "Adding DNS records for %s" % replica_fqdn api.Backend.ldap2.connect(bind_dn="cn=Directory Manager", bind_pw=dirman_password) domain = replica_fqdn.split(".") name = domain.pop(0) domain = ".".join(domain) ip = options.ip_address ip_address = str(ip) if options.reverse_zone: reverse_zone = bindinstance.normalize_zone(options.reverse_zone) else: reverse_zone = bindinstance.find_reverse_zone(ip) if reverse_zone is None and not options.no_reverse: reverse_zone = bindinstance.get_reverse_zone_default(ip) add_zone(domain) add_fwd_rr(domain, name, ip_address) if reverse_zone is not None: print "Using reverse zone %s" % reverse_zone add_reverse_zone(reverse_zone) add_ptr_rr(reverse_zone, ip_address, replica_fqdn) try: if not os.geteuid()==0: sys.exit("\nYou must be root to run this script.\n") main() except SystemExit, e: sys.exit(e) except RuntimeError, e: sys.exit(e) except Exception, e: print "preparation of replica failed: %s" % str(e) message = str(e) for str in traceback.format_tb(sys.exc_info()[2]): message = message + "\n" + str root_logger.debug(message) print message sys.exit(1)