Allow upgrading CA-less to CA-full using ipa-ca-install.

Part of https://fedorahosted.org/freeipa/ticket/3737

Reviewed-By: Rob Crittenden <rcritten@redhat.com>
This commit is contained in:
Jan Cholasta 2014-06-16 10:41:57 +02:00 committed by Petr Viktorin
parent 8bbdfff102
commit d27e77adc5
4 changed files with 227 additions and 21 deletions

View File

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

View File

@ -1,4 +1,4 @@
.\" A man page for ipa-replica-install
.\" A man page for ipa-ca-install
.\" Copyright (C) 2011 Red Hat, Inc.
.\"
.\" This program is free software; you can redistribute it and/or modify
@ -18,13 +18,15 @@
.\"
.TH "ipa-ca-install" "1" "Jun 17 2011" "FreeIPA" "FreeIPA Manual Pages"
.SH "NAME"
ipa\-ca\-install \- Install a CA on a replica
ipa\-ca\-install \- Install a CA on a server
.SH "SYNOPSIS"
ipa\-ca\-install [\fIOPTION\fR]... replica_file
ipa\-ca\-install [\fIOPTION\fR]... [replica_file]
.SH "DESCRIPTION"
Adds a CA as an IPA\-managed service. This requires that the IPA server is already installed and configured.
The replica_file is created using the ipa\-replica\-prepare utility and should be the same one used when originally installing the replica.
Alternatively, you can run ipa\-ca\-install without replica_file to upgrade from CA-less to CA-full.
.SH "OPTIONS"
\fB\-d\fR, \fB\-\-debug\fR
Enable debug logging when more verbose output is needed

View File

@ -272,6 +272,7 @@ class BasePathNamespace(object):
IPAREPLICA_CONNCHECK_LOG = "/var/log/ipareplica-conncheck.log"
IPAREPLICA_INSTALL_LOG = "/var/log/ipareplica-install.log"
IPARESTORE_LOG = "/var/log/iparestore.log"
IPASERVER_CA_INSTALL_LOG = "/var/log/ipaserver-ca-install.log"
IPASERVER_INSTALL_LOG = "/var/log/ipaserver-install.log"
IPASERVER_UNINSTALL_LOG = "/var/log/ipaserver-uninstall.log"
IPAUPGRADE_LOG = "/var/log/ipaupgrade.log"

View File

@ -618,8 +618,8 @@ class CAInstance(service.Service):
os.remove(cfg_file)
if self.external == 1:
print "The next step is to get %s signed by your CA and re-run ipa-server-install as:" % self.csr_file
print "ipa-server-install --external_cert_file=/path/to/signed_certificate --external_ca_file=/path/to/external_ca_certificate"
print "The next step is to get %s signed by your CA and re-run %s as:" % (self.csr_file, sys.argv[0])
print "%s --external_cert_file=/path/to/signed_certificate --external_ca_file=/path/to/external_ca_certificate" % sys.argv[0]
sys.exit(0)
else:
shutil.move(paths.CA_BACKUP_KEYS_P12, \
@ -777,8 +777,8 @@ class CAInstance(service.Service):
raise RuntimeError('Configuration of CA failed')
if self.external == 1:
print "The next step is to get %s signed by your CA and re-run ipa-server-install as:" % self.csr_file
print "ipa-server-install --external_cert_file=/path/to/signed_certificate --external_ca_file=/path/to/external_ca_certificate"
print "The next step is to get %s signed by your CA and re-run %s as:" % (self.csr_file, sys.argv[0])
print "%s --external_cert_file=/path/to/signed_certificate --external_ca_file=/path/to/external_ca_certificate" % sys.argv[0]
sys.exit(0)
# pkisilent makes a copy of the CA PKCS#12 file for us but gives