Add a KRA to IPA

This patch adds the capability of installing a Dogtag KRA
to an IPA instance.  With this patch,  a KRA is NOT configured
by default when ipa-server-install is run.  Rather, the command
ipa-kra-install must be executed on an instance on which a Dogtag
CA has already been configured.

The KRA shares the same tomcat instance and DS instance as the
Dogtag CA.  Moreover, the same admin user/agent (and agent cert) can
be used for both subsystems.  Certmonger is also confgured to
monitor the new subsystem certificates.

To create a clone KRA, simply execute ipa-kra-install <replica_file>
on a replica on which a Dogtag CA has already been replicated.
ipa-kra-install will use the security domain to detect whether the
system being installed is a replica, and will error out if a needed
replica file is not provided.

The install scripts have been refactored somewhat to minimize
duplication of code.  A new base class dogtagintance.py has
been introduced containing code that is common to KRA and CA
installs.  This will become very useful when we add more PKI
subsystems.

The KRA will install its database as a subtree of o=ipaca,
specifically o=ipakra,o=ipaca.  This means that replication
agreements created to replicate CA data will also replicate KRA
data.  No new replication agreements are required.

Added dogtag plugin for KRA.  This is an initial commit providing
the basic vault functionality needed for vault.  This plugin will
likely be modified as we create the code to call some of these
functions.

Part of the work for: https://fedorahosted.org/freeipa/ticket/3872

The uninstallation option in ipa-kra-install is temporarily disabled.

Reviewed-By: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Petr Viktorin <pviktori@redhat.com>
This commit is contained in:
Ade Lee
2014-03-18 11:23:30 -04:00
committed by Petr Viktorin
parent 981b399c4e
commit a25fe00c62
23 changed files with 1776 additions and 643 deletions

View File

@@ -112,6 +112,7 @@ Requires: selinux-policy >= 3.12.1-176
Requires(post): selinux-policy-base
Requires: slapi-nis >= 0.47.7
Requires: pki-ca >= 10.1.1
Requires: pki-kra >= 10.1.1
%if 0%{?rhel}
Requires: subscription-manager
%endif
@@ -570,6 +571,7 @@ fi
%{_sbindir}/ipa-restore
%{_sbindir}/ipa-ca-install
%{_sbindir}/ipa-dns-install
%{_sbindir}/ipa-kra-install
%{_sbindir}/ipa-server-install
%{_sbindir}/ipa-replica-conncheck
%{_sbindir}/ipa-replica-install

View File

@@ -1,4 +1,4 @@
# VERSION 4 - DO NOT REMOVE THIS LINE
# VERSION 5 - DO NOT REMOVE THIS LINE
ProxyRequests Off
@@ -11,7 +11,7 @@ ProxyRequests Off
</LocationMatch>
# matches for admin port and installer
<LocationMatch "^/ca/admin/ca/getCertChain|^/ca/admin/ca/getConfigEntries|^/ca/admin/ca/getCookie|^/ca/admin/ca/getStatus|^/ca/admin/ca/securityDomainLogin|^/ca/admin/ca/getDomainXML|^/ca/rest/installer/installToken|^/ca/admin/ca/updateNumberRange|^/ca/rest/securityDomain/domainInfo|^/ca/rest/account/login|^/ca/admin/ca/tokenAuthenticate|^/ca/admin/ca/updateNumberRange|^/ca/admin/ca/updateDomainXML|^/ca/rest/account/logout|^/ca/rest/securityDomain/installToken">
<LocationMatch "^/ca/admin/ca/getCertChain|^/ca/admin/ca/getConfigEntries|^/ca/admin/ca/getCookie|^/ca/admin/ca/getStatus|^/ca/admin/ca/securityDomainLogin|^/ca/admin/ca/getDomainXML|^/ca/rest/installer/installToken|^/ca/admin/ca/updateNumberRange|^/ca/rest/securityDomain/domainInfo|^/ca/rest/account/login|^/ca/admin/ca/tokenAuthenticate|^/ca/admin/ca/updateNumberRange|^/ca/admin/ca/updateDomainXML|^/ca/rest/account/logout|^/ca/rest/securityDomain/installToken|^/ca/admin/ca/updateConnector|^/ca/admin/ca/getSubsystemCert|^/kra/admin/kra/updateNumberRange|^/kra/admin/kra/getConfigEntries|^/kra/rest/config/cert/transport">
NSSOptions +StdEnvVars +ExportCertData +StrictRequire +OptRenegotiate
NSSVerifyClient none
ProxyPassMatch ajp://localhost:$DOGTAG_PORT
@@ -19,7 +19,7 @@ ProxyRequests Off
</LocationMatch>
# matches for agent port and eeca port
<LocationMatch "^/ca/agent/ca/displayBySerial|^/ca/agent/ca/doRevoke|^/ca/agent/ca/doUnrevoke|^/ca/agent/ca/updateDomainXML|^/ca/eeca/ca/profileSubmitSSLClient">
<LocationMatch "^/ca/agent/ca/displayBySerial|^/ca/agent/ca/doRevoke|^/ca/agent/ca/doUnrevoke|^/ca/agent/ca/updateDomainXML|^/ca/eeca/ca/profileSubmitSSLClient|^/kra/agent/kra/connector|^/kra/rest/agent/keyrequests|^/kra/rest/agent/keys|^/kra/rest/admin/kraconnector/remove">
NSSOptions +StdEnvVars +ExportCertData +StrictRequire +OptRenegotiate
NSSVerifyClient require
ProxyPassMatch ajp://localhost:$DOGTAG_PORT

View File

@@ -47,6 +47,7 @@ PY_EXPLICIT_FILES = \
install/tools/ipa-csreplica-manage \
install/tools/ipactl \
install/tools/ipa-dns-install \
install/tools/ipa-kra-install \
install/tools/ipa-ldap-updater \
install/tools/ipa-managed-entries \
install/tools/ipa-nis-manage \

View File

@@ -21,13 +21,12 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import os
import syslog
import tempfile
import shutil
import traceback
from ipapython import dogtag, certmonger, ipautil
from ipapython import dogtag, ipautil
from ipapython.dn import DN
from ipalib import api, errors, x509, certstore
from ipaserver.install import certs, cainstance, installutils
@@ -35,6 +34,7 @@ from ipaserver.plugins.ldap2 import ldap2
from ipaplatform import services
from ipaplatform.paths import paths
def main():
nickname = sys.argv[1]
@@ -70,8 +70,6 @@ def main():
syslog.syslog(syslog.LOG_ERR, 'No certificate %s found.' % nickname)
sys.exit(1)
cainstance.update_cert_config(nickname, cert, configured_constants)
tmpdir = tempfile.mkdtemp(prefix="tmp-")
try:
principal = str('host/%s@%s' % (api.env.host, api.env.realm))
@@ -79,6 +77,7 @@ def main():
principal)
ca = cainstance.CAInstance(host_name=api.env.host, ldapi=False)
ca.update_cert_config(nickname, cert, configured_constants)
if ca.is_renewal_master():
cainstance.update_people_entry(cert)
@@ -198,7 +197,9 @@ def main():
# off the servlet to verify that the CA is actually up and responding so
# when this returns it should be good-to-go. The CA was stopped in the
# pre-save state.
syslog.syslog(syslog.LOG_NOTICE, 'Starting %s' % dogtag_service.service_name)
syslog.syslog(
syslog.LOG_NOTICE,
'Starting %s' % dogtag_service.service_name)
try:
dogtag_service.start(dogtag_instance)
except Exception, e:

View File

@@ -7,6 +7,7 @@ SUBDIRS = \
sbin_SCRIPTS = \
ipa-ca-install \
ipa-dns-install \
ipa-kra-install \
ipa-server-install \
ipa-adtrust-install \
ipa-replica-conncheck \

View File

@@ -19,23 +19,20 @@
#
import sys
import socket
import os, shutil
import os
import shutil
from ConfigParser import RawConfigParser
from ipapython import ipautil
from ipaserver.install import installutils, service
from ipaserver.install import installutils
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, validate_external_cert)
from ipaserver.install.installutils import (
ReplicaConfig, private_ccache, create_replica_config,
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, certstore, x509
from ipalib.constants import CACERT
from ipalib import api, certstore, x509
from ipapython.dn import DN
from ipapython.config import IPAOptionParser
from ipapython import sysrestore
@@ -95,8 +92,11 @@ def parse_options():
return safe_options, options, filename
def get_dirman_password():
return installutils.read_password("Directory Manager (existing master)", confirm=False, validate=False)
return installutils.read_password(
"Directory Manager (existing master)", confirm=False, validate=False)
def install_dns_records(config, options):
@@ -115,13 +115,15 @@ def install_dns_records(config, options):
bind.add_ipa_ca_dns_records(config.host_name, config.domain_name)
finally:
if api.Backend.ldap2.isconnected() and disconnect:
api.Backend.ldap2.disconnect()
api.Backend.ldap2.disconnect()
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))
root_logger.debug('IPA version %s' % version.VENDOR_VERSION)
root_logger.debug('%s was invoked with argument "%s" and options: %s',
sys.argv[0], filename, safe_options)
root_logger.debug('IPA version %s', version.VENDOR_VERSION)
if not ipautil.file_exists(filename):
sys.exit("Replica file %s does not exist" % filename)
@@ -151,38 +153,13 @@ def install_replica(safe_options, options, filename):
sys.exit("Directory Manager password required")
if not options.admin_password and not options.skip_conncheck and \
options.unattended:
sys.exit('admin password required')
options.unattended:
sys.exit('admin password required')
try:
top_dir, dir = expand_replica_info(filename, dirman_password)
global REPLICA_INFO_TOP_DIR
REPLICA_INFO_TOP_DIR = top_dir
except Exception, e:
print "ERROR: Failed to decrypt or open the replica file."
print "Verify you entered the correct Directory Manager password."
sys.exit(1)
config = ReplicaConfig()
read_replica_info(dir, config)
config.dirman_password = dirman_password
try:
host = get_host_name(options.no_host_dns)
except BadHostError, e:
root_logger.error(str(e))
sys.exit(1)
if config.host_name != host:
try:
print "This replica was created for '%s' but this machine is named '%s'" % (config.host_name, host)
if not ipautil.user_input("This may cause problems. Continue?", True):
sys.exit(0)
config.host_name = host
print ""
except KeyboardInterrupt:
sys.exit(0)
config.dir = dir
config = create_replica_config(dirman_password, filename, options)
global REPLICA_INFO_TOP_DIR
REPLICA_INFO_TOP_DIR = config.top_dir
config.setup_ca = True
config.ca_ds_port = read_replica_info_dogtag_port(config.dir)
if not ipautil.file_exists(config.dir + "/cacert.p12"):
print 'CA cannot be installed in CA-less setup.'
@@ -206,7 +183,7 @@ def install_replica(safe_options, options, filename):
ipautil.realm_to_suffix(config.realm_name))
# This is done within stopped_service context, which restarts CA
CA.enable_client_auth_to_db()
CA.enable_client_auth_to_db(CA.dogtag_constants.CS_CFG_PATH)
# Install CA DNS records
install_dns_records(config, options)
@@ -225,12 +202,13 @@ def install_replica(safe_options, options, filename):
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)
"%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)
@@ -316,7 +294,8 @@ def install_master(safe_options, options):
"cannot continue." % (subject, db.secdir))
sys.exit(1)
ca = cainstance.CAInstance(realm_name, certs.NSS_DIR,
ca = cainstance.CAInstance(
realm_name, certs.NSS_DIR,
dogtag_constants=dogtag.install_constants)
ca.create_ra_agent_db = False
if external == 0:
@@ -338,7 +317,7 @@ def install_master(safe_options, options):
ca.ldap_enable('CA', host_name, dm_password,
ipautil.realm_to_suffix(realm_name), ['caRenewalMaster'])
ca.enable_client_auth_to_db()
ca.enable_client_auth_to_db(ca.dogtag_constants.CS_CFG_PATH)
# Install CA DNS records
config = ReplicaConfig()
@@ -396,6 +375,7 @@ def install_master(safe_options, options):
ca.start(ca.dogtag_constants.PKI_INSTANCE_NAME)
def main():
safe_options, options, filename = parse_options()
@@ -416,8 +396,8 @@ if __name__ == '__main__':
try:
with private_ccache():
installutils.run_script(main, log_file_name=log_file_name,
operation_name='ipa-ca-install',
fail_message=fail_message)
operation_name='ipa-ca-install',
fail_message=fail_message)
finally:
# always try to remove decrypted replica file
try:

View File

@@ -32,6 +32,7 @@ from ipalib import api, errors, util
from ipaplatform.paths import paths
from ipapython.config import IPAOptionParser
from ipapython.ipa_log_manager import standard_logging_setup, root_logger
from ipapython.ipautil import DN
log_file_name = paths.IPASERVER_INSTALL_LOG

View File

@@ -0,0 +1,23 @@
#! /usr/bin/python2 -E
# Authors: Ade Lee <alee@redhat.com>
#
# Copyright (C) 2014 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 <http://www.gnu.org/licenses/>.
#
from ipaserver.install.ipa_kra_install import KRAInstall
KRAInstall.run_cli()

View File

@@ -36,12 +36,12 @@ from ipaserver.install import bindinstance, httpinstance, ntpinstance
from ipaserver.install import memcacheinstance
from ipaserver.install import otpdinstance
from ipaserver.install.replication import replica_conn_check, ReplicationManager
from ipaserver.install.installutils import (ReplicaConfig, expand_replica_info,
read_replica_info, get_host_name, BadHostError, private_ccache,
read_replica_info_dogtag_port)
from ipaserver.install.installutils import (
create_replica_config, read_replica_info_kra_enabled, private_ccache)
from ipaserver.plugins.ldap2 import ldap2
from ipaserver.install import cainstance
from ipalib import api, errors, util, x509, certstore
from ipaserver.install import krainstance
from ipalib import api, errors, util, certstore, x509
from ipalib.constants import CACERT
from ipapython import version
from ipapython.config import IPAOptionParser
@@ -55,8 +55,8 @@ from ipaplatform import services
from ipaplatform.paths import paths
log_file_name = paths.IPAREPLICA_INSTALL_LOG
REPLICA_INFO_TOP_DIR = None
DIRMAN_DN = DN(('cn', 'directory manager'))
REPLICA_INFO_TOP_DIR = None
def parse_options():
usage = "%prog [options] REPLICA_FILE"
@@ -65,6 +65,8 @@ def parse_options():
basic_group = OptionGroup(parser, "basic options")
basic_group.add_option("--setup-ca", dest="setup_ca", action="store_true",
default=False, help="configure a dogtag CA")
basic_group.add_option("--setup-kra", dest="setup_kra", action="store_true",
default=False, help="configure a dogtag KRA")
basic_group.add_option("--ip-address", dest="ip_address",
type="ip", ip_local=True,
help="Replica server IP Address")
@@ -206,6 +208,7 @@ def install_krb(config, setup_pkinit=False):
return krb
def install_ca_cert(ldap, base_dn, realm, cafile):
try:
try:
@@ -508,44 +511,24 @@ def main():
if dirman_password is None:
sys.exit("Directory Manager password required")
try:
top_dir, dir = expand_replica_info(filename, dirman_password)
global REPLICA_INFO_TOP_DIR
REPLICA_INFO_TOP_DIR = top_dir
except Exception, e:
print "ERROR: Failed to decrypt or open the replica file."
print "Verify you entered the correct Directory Manager password."
sys.exit(1)
config = ReplicaConfig()
read_replica_info(dir, config)
root_logger.debug('Installing replica file with version %d (0 means no version in prepared file).' % config.version)
if config.version and config.version > version.NUM_VERSION:
root_logger.error('A replica file from a newer release (%d) cannot be installed on an older version (%d)' % (config.version, version.NUM_VERSION))
sys.exit(1)
config.dirman_password = dirman_password
try:
host = get_host_name(options.no_host_dns)
except BadHostError, e:
root_logger.error(str(e))
sys.exit(1)
if config.host_name != host:
try:
print "This replica was created for '%s' but this machine is named '%s'" % (config.host_name, host)
if not ipautil.user_input("This may cause problems. Continue?", False):
sys.exit(0)
config.host_name = host
print ""
except KeyboardInterrupt:
sys.exit(0)
config.dir = dir
config = create_replica_config(dirman_password, filename, options)
global REPLICA_INFO_TOP_DIR
REPLICA_INFO_TOP_DIR = config.top_dir
config.setup_ca = options.setup_ca
config.ca_ds_port = read_replica_info_dogtag_port(config.dir)
if config.setup_ca and not ipautil.file_exists(config.dir + "/cacert.p12"):
print 'CA cannot be installed in CA-less setup.'
sys.exit(1)
config.setup_kra = options.setup_kra
if config.setup_kra:
if not config.setup_ca:
print "CA must be installed with the KRA"
sys.exit(1)
if not read_replica_info_kra_enabled(config.dir):
print "KRA is not installed on the master system"
sys.exit(1)
installutils.verify_fqdn(config.master_host_name, options.no_host_dns)
# check connection
@@ -579,6 +562,9 @@ def main():
else:
fd.write("enable_ra=False\n")
fd.write("ra_plugin=none\n")
fd.write("enable_kra=%s\n" % config.setup_kra)
fd.write("mode=production\n")
fd.close()
finally:
@@ -611,7 +597,7 @@ def main():
# Check that we don't already have a replication agreement
try:
(agreement_cn, agreement_dn) = replman.agreement_dn(host)
(agreement_cn, agreement_dn) = replman.agreement_dn(config.host_name)
entry = conn.get_entry(agreement_dn, ['*'])
except errors.NotFound:
pass
@@ -621,20 +607,20 @@ def main():
print ('A replication agreement for this host already exists. '
'It needs to be removed.')
print "Run this on the master that generated the info file:"
print " %% ipa-replica-manage del %s --force" % host
print " %% ipa-replica-manage del %s --force" % config.host_name
exit(3)
# Check pre-existing host entry
try:
entry = conn.find_entries(u'fqdn=%s' % host, ['fqdn'], DN(api.env.container_host, api.env.basedn))
entry = conn.find_entries(u'fqdn=%s' % config.host_name, ['fqdn'], DN(api.env.container_host, api.env.basedn))
except errors.NotFound:
pass
else:
root_logger.info(
'Error: Host %s already exists on the master server.' % host)
print 'The host %s already exists on the master server.' % host
'Error: Host %s already exists on the master server.' % config.host_name)
print 'The host %s already exists on the master server.' % config.host_name
print "You should remove it before proceeding:"
print " %% ipa host-del %s" % host
print " %% ipa host-del %s" % config.host_name
exit(3)
# Install CA cert so that we can do SSL connections with ldap
@@ -694,7 +680,7 @@ def main():
ipautil.realm_to_suffix(config.realm_name))
# This is done within stopped_service context, which restarts CA
CA.enable_client_auth_to_db()
CA.enable_client_auth_to_db(CA.dogtag_constants.CS_CFG_PATH)
krb = install_krb(config, setup_pkinit=options.setup_pkinit)
http = install_http(config, auto_redirect=options.ui_redirect)
@@ -705,7 +691,7 @@ def main():
if CA:
CA.configure_certmonger_renewal()
CA.import_ra_cert(dir + "/ra.p12")
CA.import_ra_cert(config.dir + "/ra.p12")
CA.fix_ra_perms()
services.knownservices.httpd.restart()
@@ -717,9 +703,14 @@ def main():
service.print_msg("Applying LDAP updates")
ds.apply_updates()
# Restart ds and krb after configurations have been changed
service.print_msg("Restarting the directory server")
ds.restart()
if options.setup_kra:
kra = krainstance.install_replica_kra(config)
service.print_msg("Restarting the directory server")
ds.restart()
kra.enable_client_auth_to_db(kra.dogtag_constants.KRA_CS_CFG_PATH)
else:
service.print_msg("Restarting the directory server")
ds.restart()
service.print_msg("Restarting the KDC")
krb.restart()

View File

@@ -3,7 +3,7 @@
# Simo Sorce <ssorce@redhat.com>
# Rob Crittenden <rcritten@redhat.com>
#
# Copyright (C) 2007-2010 Red Hat
# Copyright (C) 2007-2014 Red Hat
# see file 'COPYING' for use and warranty information
#
# This program is free software; you can redistribute it and/or modify
@@ -53,6 +53,7 @@ from ipaserver.install import httpinstance
from ipaserver.install import ntpinstance
from ipaserver.install import certs
from ipaserver.install import cainstance
from ipaserver.install import krainstance
from ipaserver.install import memcacheinstance
from ipaserver.install import otpdinstance
from ipaserver.install import sysupgrade
@@ -520,11 +521,20 @@ def uninstall():
dogtag_constants=dogtag_constants)
if cads_instance.is_configured():
cads_instance.uninstall()
cainstance.stop_tracking_certificates(dogtag_constants)
kra_instance = krainstance.KRAInstance(
api.env.realm, dogtag_constants=dogtag_constants)
kra_instance.stop_tracking_certificates(dogtag_constants)
if kra_instance.is_installed():
kra_instance.uninstall()
ca_instance = cainstance.CAInstance(
api.env.realm, certs.NSS_DIR, dogtag_constants=dogtag_constants)
ca_instance.stop_tracking_certificates(dogtag_constants)
ca_instance.stop_tracking_agent_certificate(dogtag_constants)
if ca_instance.is_configured():
ca_instance.uninstall()
bindinstance.BindInstance(fstore).uninstall()
httpinstance.HTTPInstance(fstore).uninstall()
krbinstance.KrbInstance(fstore).uninstall()
@@ -757,8 +767,13 @@ def main():
# We only set up the CA if the PKCS#12 options are not given.
if options.dirsrv_pkcs12:
setup_ca = False
setup_kra = False
else:
setup_ca = True
# setup_kra is set to False until Dogtag 10.2 is available for IPA to consume
# Until then users that want to install the KRA need to use ipa-install-kra
# TODO set setup_kra = True when Dogtag 10.2 is available
setup_kra = False
# Figure out what external CA step we're in. See cainstance.py for more
# info on the 3 states.
@@ -775,6 +790,8 @@ def main():
print "This includes:"
if setup_ca:
print " * Configure a stand-alone CA (dogtag) for certificate management"
if setup_kra:
print " * Configure a stand-alone KRA (dogtag) for key storage"
if options.conf_ntp:
print " * Configure the Network Time Daemon (ntpd)"
print " * Create and configure an instance of Directory Server"
@@ -1021,6 +1038,7 @@ def main():
else:
fd.write("enable_ra=False\n")
fd.write("ra_plugin=none\n")
fd.write("enable_kra=%s\n" % setup_kra)
fd.write("mode=production\n")
fd.close()
@@ -1122,7 +1140,7 @@ def main():
ipautil.realm_to_suffix(realm_name), ['caRenewalMaster'])
# This is done within stopped_service context, which restarts CA
ca.enable_client_auth_to_db()
ca.enable_client_auth_to_db(ca.dogtag_constants.CS_CFG_PATH)
krb = krbinstance.KrbInstance(fstore)
if options.pkinit_pkcs12:
@@ -1204,6 +1222,20 @@ def main():
service.print_msg("Restarting the web server")
http.restart()
if setup_kra:
kra = krainstance.KRAInstance(realm_name,
dogtag_constants=dogtag.install_constants)
kra.configure_instance(host_name, domain_name, dm_password,
dm_password, subject_base=options.subject)
# This is done within stopped_service context, which restarts KRA
service.print_msg("Restarting the directory server")
ds.restart()
service.print_msg("Enabling KRA to authenticate with the database "
"using client certificates")
kra.enable_client_auth_to_db(kra.dogtag_constants.KRA_CS_CFG_PATH)
# Set the admin user kerberos password
ds.change_admin_password(admin_password)
@@ -1256,9 +1288,11 @@ def main():
print ""
if setup_ca:
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"
print "Be sure to back up the CA certificates stored in " + paths.CACERT_P12
if setup_kra:
print "and the KRA certificates stored in " + paths.KRACERT_P12
print "These files are required to create replicas. The password for these"
print "files is the Directory Manager password"
else:
print "In order for Firefox autoconfiguration to work you will need to"
print "use a SSL signing certificate. See the IPA documentation for more details."

View File

@@ -537,12 +537,15 @@ def named_update_gssapi_configuration():
return False
try:
bindinstance.named_conf_set_directive('tkey-gssapi-credential', None,
bindinstance.NAMED_SECTION_OPTIONS)
bindinstance.named_conf_set_directive('tkey-domain', None,
bindinstance.NAMED_SECTION_OPTIONS)
bindinstance.named_conf_set_directive('tkey-gssapi-keytab', paths.NAMED_KEYTAB,
bindinstance.NAMED_SECTION_OPTIONS)
bindinstance.named_conf_set_directive(
'tkey-gssapi-credential', None,
bindinstance.NAMED_SECTION_OPTIONS)
bindinstance.named_conf_set_directive(
'tkey-domain', None,
bindinstance.NAMED_SECTION_OPTIONS)
bindinstance.named_conf_set_directive(
'tkey-gssapi-keytab', paths.NAMED_KEYTAB,
bindinstance.NAMED_SECTION_OPTIONS)
except IOError, e:
root_logger.error('Cannot update GSSAPI configuration in %s: %s',
bindinstance.NAMED_CONF, e)
@@ -553,6 +556,7 @@ def named_update_gssapi_configuration():
sysupgrade.set_upgrade_state('named.conf', 'gssapi_updated', True)
return True
def named_update_pid_file():
"""
Make sure that named reads the pid file from the right file
@@ -723,7 +727,7 @@ def certificate_renewal_update(ca):
# Ok, now we need to stop tracking, then we can start tracking them
# again with new configuration:
cainstance.stop_tracking_certificates(dogtag_constants)
ca.stop_tracking_certificates(dogtag_constants)
if not sysupgrade.get_upgrade_state('dogtag',
'certificate_renewal_update_1'):
@@ -884,71 +888,9 @@ def add_ca_dns_records():
def find_subject_base():
"""
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
See the docstring in dsinstance.DsInstance for details.
"""
root_logger.debug('Trying to find certificate subject base in sysupgrade')
subject_base = sysupgrade.get_upgrade_state('certmap.conf', 'subject_base')
if subject_base:
root_logger.debug(
'Found certificate subject base in sysupgrade: %s',
subject_base
)
return subject_base
root_logger.debug('Unable to find certificate subject base in sysupgrade')
root_logger.debug('Trying to find certificate subject base in DS')
ds_is_running = services.knownservices.dirsrv.is_running()
if not ds_is_running:
try:
services.knownservices.dirsrv.start()
except ipautil.CalledProcessError as e:
root_logger.error('Cannot start DS to find certificate '
'subject base: %s', e)
else:
ds_is_running = True
if ds_is_running:
try:
api.Backend.ldap2.connect(autobind=True)
except ipalib.errors.PublicError, e:
root_logger.error('Cannot connect to DS to find certificate '
'subject base: %s', e)
else:
ret = api.Command['config_show']()
api.Backend.ldap2.disconnect()
subject_base = str(ret['result']['ipacertificatesubjectbase'][0])
root_logger.debug(
'Found certificate subject base in DS: %s',
subject_base
)
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 = dsinstance.config_dirname(
dsinstance.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)
subject_base = dsinstance.DsInstance().find_subject_base()
if subject_base:
sysupgrade.set_upgrade_state(
@@ -958,8 +900,6 @@ def find_subject_base():
)
return subject_base
root_logger.debug('Unable to find certificate subject base in '
'certmap.conf')
root_logger.error('Unable to determine certificate subject base. '
'certmap.conf will not be updated.')

View File

@@ -155,6 +155,8 @@ DEFAULT_CONFIG = (
('ca_agent_install_port', None),
('ca_ee_install_port', None),
# KRA plugin
('kra_host', FQDN), # Set in Env._finalize_core()
# Special CLI:
('prompt_all', False),

View File

@@ -125,7 +125,8 @@ class BasePathNamespace(object):
HOME_DIR = "/home"
ROOT_IPA_CACHE = "/root/.ipa_cache"
ROOT_PKI = "/root/.pki"
CA_AGENT_P12 = "/root/ca-agent.p12"
DOGTAG_AGENT_P12 = "/root/ca-agent.p12"
DOGTAG_AGENT_PEM = "/etc/httpd/alias/agent.pem"
CACERT_P12 = "/root/cacert.p12"
ROOT_IPA_CSR = "/root/ipa.csr"
ROOT_TMP_CA_P12 = "/root/tmp-ca.p12"
@@ -218,7 +219,7 @@ class BasePathNamespace(object):
SCHEMA_COMPAT_ULDIF = "/usr/share/ipa/schema_compat.uldif"
IPA_JS_PLUGINS_DIR = "/usr/share/ipa/ui/js/plugins"
UPDATES_DIR = "/usr/share/ipa/updates/"
PKI_CONF_SERVER_XML = "/usr/share/pki/ca/conf/server.xml"
PKI_CONF_SERVER_XML_TEMPLATE = "/usr/share/pki/%s/conf/server.xml"
CACHE_IPA_SESSIONS = "/var/cache/ipa/sessions"
VAR_KERBEROS_KRB5KDC_DIR = "/var/kerberos/krb5kdc/"
VAR_KRB5KDC_K5_REALM = "/var/kerberos/krb5kdc/.k5."
@@ -254,6 +255,8 @@ class BasePathNamespace(object):
PKI_ALIAS_CA_P12 = "/var/lib/pki-ca/alias/ca.p12"
VAR_LIB_PKI_TOMCAT_DIR = "/var/lib/pki/pki-tomcat"
CA_BACKUP_KEYS_P12 = "/var/lib/pki/pki-tomcat/alias/ca_backup_keys.p12"
KRA_BACKUP_KEYS_P12 = "/var/lib/pki/pki-tomcat/alias/kra_backup_keys.p12"
KRACERT_P12 = "/root/kracert.p12"
SAMBA_DIR = "/var/lib/samba/"
SSSD_MC_GROUP = "/var/lib/sss/mc/group"
SSSD_MC_PASSWD = "/var/lib/sss/mc/passwd"
@@ -282,6 +285,8 @@ class BasePathNamespace(object):
PKI_CA_LOG_DIR = "/var/log/pki-ca"
PKI_CA_INSTALL_LOG = "/var/log/pki-ca-install.log"
PKI_CA_UNINSTALL_LOG = "/var/log/pki-ca-uninstall.log"
PKI_KRA_INSTALL_LOG = "/var/log/pki-kra-install.log"
PKI_KRA_UNINSTALL_LOG = "/var/log/pki-kra-uninstall.log"
VAR_LOG_PKI_DIR = "/var/log/pki/"
TOMCAT_TOPLEVEL_DIR = "/var/log/pki/pki-tomcat"
TOMCAT_CA_DIR = "/var/log/pki/pki-tomcat/ca"
@@ -296,5 +301,8 @@ class BasePathNamespace(object):
KRB5CC_SAMBA = "/var/run/samba/krb5cc_samba"
SLAPD_INSTANCE_SOCKET_TEMPLATE = "/var/run/slapd-%s.socket"
ALL_SLAPD_INSTANCE_SOCKETS = "/var/run/slapd-*.socket"
ADMIN_CERT_PATH = '/root/.dogtag/pki-tomcat/ca_admin.cert'
ENTROPY_AVAIL = '/proc/sys/kernel/random/entropy_avail'
path_namespace = BasePathNamespace

View File

@@ -24,10 +24,9 @@ import ConfigParser
from urllib import urlencode
import nss.nss as nss
from nss.error import NSPRError
from ipalib import api, errors
from ipalib.errors import NetworkError, CertificateOperationError
from ipalib.errors import NetworkError
from ipalib.text import _
from ipapython import nsslib, ipautil
from ipaplatform.paths import paths
@@ -42,6 +41,7 @@ from ipapython.ipa_log_manager import *
# The configured_constants() function below provides constants relevant to
# the configured version.
class Dogtag10Constants(object):
DOGTAG_VERSION = 10
UNSECURE_PORT = 8080
@@ -63,6 +63,7 @@ class Dogtag10Constants(object):
SERVICE_PROFILE_DIR = '%s/ca/profiles/ca' % PKI_ROOT
ALIAS_DIR = paths.PKI_TOMCAT_ALIAS_DIR.rstrip('/')
SYSCONFIG_FILE_PATH = '%s/%s' % (paths.ETC_SYSCONFIG_DIR, PKI_INSTANCE_NAME)
KRA_CS_CFG_PATH = '%s/conf/kra/CS.cfg' % PKI_ROOT
SERVICE_NAME = 'pki_tomcatd'
@@ -165,7 +166,8 @@ def get_ca_certchain(ca_host=None, dogtag_constants=None):
if dogtag_constants is None:
dogtag_constants = configured_constants()
chain = None
conn = httplib.HTTPConnection(ca_host,
conn = httplib.HTTPConnection(
ca_host,
api.env.ca_install_port or dogtag_constants.UNSECURE_PORT)
conn.request("GET", "/ca/ee/ca/getCertChain")
res = conn.getresponse()
@@ -244,7 +246,7 @@ def https_request(host, port, url, secdir, password, nickname, **kw):
body = urlencode(kw)
return _httplib_request(
'https', host, port, url, connection_factory, body)
'https', host, port, url, connection_factory, body)
def http_request(host, port, url, **kw):
@@ -291,7 +293,8 @@ def _httplib_request(
root_logger.debug('request body %r', request_body)
try:
conn = connection_factory(host, port)
conn.request('POST', uri,
conn.request(
'POST', uri,
body=request_body,
headers={'Content-type': 'application/x-www-form-urlencoded'},
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,399 @@
# Authors: Ade Lee <alee@redhat.com>
#
# Copyright (C) 2014 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 <http://www.gnu.org/licenses/>.
#
import base64
import os
import shutil
import tempfile
import traceback
from pki.client import PKIConnection
import pki.system
from ipaplatform import services
from ipaplatform.paths import paths
from ipapython import certmonger
from ipapython import dogtag
from ipapython import ipaldap
from ipapython import ipautil
from ipapython.dn import DN
from ipaserver.install import service
from ipaserver.install import installutils
from ipaserver.install.installutils import stopped_service
from ipapython.ipa_log_manager import log_mgr
DEFAULT_DSPORT = dogtag.install_constants.DS_PORT
PKI_USER = "pkiuser"
PKI_DS_USER = dogtag.install_constants.DS_USER
def check_inst(subsystem):
"""
Validate that the appropriate dogtag/RHCS packages have been installed.
"""
# Check for a couple of binaries we need
if not os.path.exists(dogtag.install_constants.SPAWN_BINARY):
return False
if not os.path.exists(dogtag.install_constants.DESTROY_BINARY):
return False
if not os.path.exists(paths.PKI_CONF_SERVER_XML_TEMPLATE % subsystem):
return False
return True
def get_security_domain():
"""
Get the security domain from the REST interface on the local Dogtag CA
This function will succeed if the local dogtag CA is up.
"""
connection = PKIConnection()
domain_client = pki.system.SecurityDomainClient(connection)
info = domain_client.get_security_domain_info()
return info
def is_installing_replica(sys_type):
"""
We expect only one of each type of Dogtag subsystem in an IPA deployment.
That means that if a subsystem of the specified type has already been
deployed - and therefore appears in the security domain - then we must be
installing a replica.
"""
info = get_security_domain()
try:
sys_list = info.systems[sys_type]
return len(sys_list.hosts) > 0
except KeyError:
return False
class DogtagInstance(service.Service):
"""
This is the base class for a Dogtag 10+ instance, which uses a
shared tomcat instance and DS to host the relevant subsystems.
It contains functions that will be common to installations of the
CA, KRA, and eventually TKS and TPS.
"""
def __init__(self, realm, subsystem, service_desc, dogtag_constants=None,
host_name=None, dm_password=None, ldapi=True):
"""Initializer"""
if dogtag_constants is None:
dogtag_constants = dogtag.configured_constants()
super(DogtagInstance, self).__init__(
'%sd' % dogtag_constants.PKI_INSTANCE_NAME,
service_desc=service_desc,
dm_password=dm_password,
ldapi=ldapi
)
self.dogtag_constants = dogtag_constants
self.realm = realm
self.dm_password = None
self.admin_password = None
self.fqdn = host_name
self.domain = None
self.pkcs12_info = None
self.clone = False
self.basedn = DN(('o', 'ipa%s' % subsystem.lower()))
self.admin_user = DN(('uid', 'admin'), ('ou', 'people'), ('o', 'ipaca'))
self.agent_db = tempfile.mkdtemp(prefix="tmp-")
self.ds_port = DEFAULT_DSPORT
self.server_root = dogtag_constants.SERVER_ROOT
self.subsystem = subsystem
self.security_domain_name = "IPA"
self.tracking_reqs = None
# replication parameters
self.master_host = None
self.master_replication_port = None
self.subject_base = None
self.log = log_mgr.get_logger(self)
def __del__(self):
shutil.rmtree(self.agent_db, ignore_errors=True)
def is_installed(self):
"""
Determine if subsystem instance has been installed.
Returns True/False
"""
return os.path.exists(os.path.join(
self.server_root, self.dogtag_constants.PKI_INSTANCE_NAME,
self.subsystem.lower()))
def spawn_instance(self, cfg_file, nolog_list=None):
"""
Create and configure a new Dogtag instance using pkispawn.
Passes in a configuration file with IPA-specific
parameters.
"""
subsystem = self.subsystem
# Define the things we don't want logged
if nolog_list is None:
nolog_list = []
nolog = tuple(nolog_list) + (self.admin_password, self.dm_password)
args = [paths.PKISPAWN,
"-s", subsystem,
"-f", cfg_file]
with open(cfg_file) as f:
self.log.debug(
'Contents of pkispawn configuration file (%s):\n%s',
cfg_file, ipautil.nolog_replace(f.read(), nolog))
try:
ipautil.run(args, nolog=nolog)
except ipautil.CalledProcessError, e:
self.log.critical("failed to configure %s instance %s",
subsystem, e)
raise RuntimeError('Configuration of %s failed' % subsystem)
def enable(self):
self.backup_state("enabled", self.is_enabled())
# We do not let the system start IPA components on its own,
# Instead we reply on the IPA init script to start only enabled
# components as found in our LDAP configuration tree
# We need to install DS before we can actually ldap_enable a service.
# so actual enablement is delayed.
def restart_instance(self):
try:
self.restart(self.dogtag_constants.PKI_INSTANCE_NAME)
except Exception:
self.log.debug(traceback.format_exc())
self.log.critical(
"Failed to restart the Dogtag instance."
"See the installation log for details.")
def start_instance(self):
try:
self.start(self.dogtag_constants.PKI_INSTANCE_NAME)
except Exception:
self.log.debug(traceback.format_exc())
self.log.critical(
"Failed to restart the Dogtag instance."
"See the installation log for details.")
def stop_instance(self):
try:
self.stop(self.dogtag_constants.PKI_INSTANCE_NAME)
except Exception:
self.log.debug(traceback.format_exc())
self.log.critical(
"Failed to restart the Dogtag instance."
"See the installation log for details.")
def enable_client_auth_to_db(self, config):
"""
Enable client auth connection to the internal db.
Path to CS.cfg config file passed in.
"""
with stopped_service(
self.dogtag_constants.SERVICE_NAME,
instance_name=self.dogtag_constants.PKI_INSTANCE_NAME):
installutils.set_directive(
config,
'authz.instance.DirAclAuthz.ldap.ldapauth.authtype',
'SslClientAuth', quotes=False, separator='=')
installutils.set_directive(
config,
'authz.instance.DirAclAuthz.ldap.ldapauth.bindDN',
'uid=pkidbuser,ou=people,o=ipaca', quotes=False, separator='=')
installutils.set_directive(
config,
'authz.instance.DirAclAuthz.ldap.ldapauth.clientCertNickname',
'subsystemCert cert-pki-ca', quotes=False, separator='=')
installutils.set_directive(
config,
'authz.instance.DirAclAuthz.ldap.ldapconn.port',
str(dogtag.install_constants.DS_SECURE_PORT),
quotes=False, separator='=')
installutils.set_directive(
config,
'authz.instance.DirAclAuthz.ldap.ldapconn.secureConn',
'true', quotes=False, separator='=')
installutils.set_directive(
config,
'internaldb.ldapauth.authtype',
'SslClientAuth', quotes=False, separator='=')
installutils.set_directive(
config,
'internaldb.ldapauth.bindDN',
'uid=pkidbuser,ou=people,o=ipaca', quotes=False, separator='=')
installutils.set_directive(
config,
'internaldb.ldapauth.clientCertNickname',
'subsystemCert cert-pki-ca', quotes=False, separator='=')
installutils.set_directive(
config,
'internaldb.ldapconn.port',
str(dogtag.install_constants.DS_SECURE_PORT),
quotes=False, separator='=')
installutils.set_directive(
config,
'internaldb.ldapconn.secureConn', 'true', quotes=False,
separator='=')
def uninstall(self):
if self.is_installed():
self.print_msg("Unconfiguring %s" % self.subsystem)
try:
ipautil.run([paths.PKIDESTROY, "-i",
self.dogtag_constants.PKI_INSTANCE_NAME,
"-s", self.subsystem])
except ipautil.CalledProcessError, e:
self.log.critical("failed to uninstall %s instance %s",
self.subsystem, e)
def http_proxy(self):
""" Update the http proxy file """
template_filename = ipautil.SHARE_DIR + "ipa-pki-proxy.conf"
sub_dict = dict(
DOGTAG_PORT=self.dogtag_constants.AJP_PORT,
CLONE='' if self.clone else '#',
FQDN=self.fqdn,
)
template = ipautil.template_file(template_filename, sub_dict)
with open(paths.HTTPD_IPA_PKI_PROXY_CONF, "w") as fd:
fd.write(template)
def __get_pin(self):
try:
return certmonger.get_pin('internal',
dogtag_constants=self.dogtag_constants)
except IOError, e:
self.log.debug(
'Unable to determine PIN for the Dogtag instance: %s', e)
raise RuntimeError(e)
def configure_renewal(self, reqs=None):
""" Configure certmonger to renew system certs
@param reqs: list of nicknames and profiles
"""
cmonger = services.knownservices.certmonger
cmonger.enable()
services.knownservices.messagebus.start()
cmonger.start()
pin = self.__get_pin()
if reqs is None:
reqs = self.tracking_reqs
for nickname, profile in reqs:
try:
certmonger.dogtag_start_tracking(
ca='dogtag-ipa-ca-renew-agent',
nickname=nickname,
pin=pin,
pinfile=None,
secdir=self.dogtag_constants.ALIAS_DIR,
pre_command='stop_pkicad',
post_command='renew_ca_cert "%s"' % nickname,
profile=profile)
except (ipautil.CalledProcessError, RuntimeError), e:
self.log.error(
"certmonger failed to start tracking certificate: %s", e)
def stop_tracking_certificates(self, dogtag_constants, reqs=None):
"""Stop tracking our certificates. Called on uninstall.
"""
cmonger = services.knownservices.certmonger
services.knownservices.messagebus.start()
cmonger.start()
if reqs is None:
reqs = self.tracking_reqs
for nickname, _profile in reqs:
try:
certmonger.stop_tracking(
dogtag_constants.ALIAS_DIR, nickname=nickname)
except (ipautil.CalledProcessError, RuntimeError), e:
self.log.error(
"certmonger failed to stop tracking certificate: %s", e)
cmonger.stop()
@staticmethod
def update_cert_cs_cfg(nickname, cert, directives, cs_cfg,
dogtag_constants=None):
"""
When renewing a Dogtag subsystem certificate the configuration file
needs to get the new certificate as well.
nickname is one of the known nicknames.
cert is a DER-encoded certificate.
directives is the list of directives to be updated for the subsystem
cs_cfg is the path to the CS.cfg file
"""
if dogtag_constants is None:
dogtag_constants = dogtag.configured_constants()
with stopped_service(dogtag_constants.SERVICE_NAME,
instance_name=dogtag_constants.PKI_INSTANCE_NAME):
installutils.set_directive(
cs_cfg,
directives[nickname],
base64.b64encode(cert),
quotes=False,
separator='=')
def get_admin_cert(self):
"""
Get the certificate for the admin user by checking the ldap entry
for the user. There should be only one certificate per user.
"""
self.log.debug('Trying to find the certificate for the admin user')
conn = None
try:
conn = ipaldap.IPAdmin(self.fqdn, self.ds_port)
conn.do_simple_bind(
DN(('cn', 'Directory Manager')),
self.dm_password)
entry_attrs = conn.get_entry(self.admin_user, ['usercertificate'])
admin_cert = entry_attrs.get('usercertificate')[0]
# TODO(edewata) Add check to warn if there is more than one cert.
finally:
if conn is not None:
conn.unbind()
return base64.b64encode(admin_cert)

View File

@@ -25,7 +25,6 @@ import os
import re
import time
import tempfile
import base64
import stat
import grp
@@ -38,7 +37,9 @@ import ldap
from ipaserver.install import ldapupdate
from ipaserver.install import replication
from ipaserver.install import sysupgrade
from ipalib import errors, certstore
from ipalib import api
from ipalib import certstore
from ipalib import errors
from ipaplatform.tasks import tasks
from ipalib.constants import CACERT
from ipapython.dn import DN
@@ -952,3 +953,85 @@ class DsInstance(service.Service):
pass
self.ldap_disconnect()
def find_subject_base(self):
"""
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
Note that this method can only be executed AFTER the ipa server
is configured, the api is initialized elsewhere and
that a ticket already have been acquired.
"""
root_logger.debug(
'Trying to find certificate subject base in sysupgrade')
subject_base = sysupgrade.get_upgrade_state(
'certmap.conf', 'subject_base')
if subject_base:
root_logger.debug(
'Found certificate subject base in sysupgrade: %s',
subject_base)
return subject_base
root_logger.debug(
'Unable to find certificate subject base in sysupgrade')
root_logger.debug(
'Trying to find certificate subject base in DS')
ds_is_running = is_ds_running()
if not ds_is_running:
try:
self.start()
ds_is_running = True
except ipautil.CalledProcessError as e:
root_logger.error('Cannot start DS to find certificate '
'subject base: %s', e)
if ds_is_running:
try:
api.Backend.ldap2.connect(autobind=True)
ret = api.Command['config_show']()
subject_base = str(
ret['result']['ipacertificatesubjectbase'][0])
root_logger.debug(
'Found certificate subject base in DS: %s', subject_base)
except errors.PublicError, e:
root_logger.error('Cannot connect to DS to find certificate '
'subject base: %s', e)
finally:
try:
api.Backend.ldap2.disconnect()
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(
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
root_logger.debug('Unable to find certificate subject base in '
'certmap.conf')
return None

View File

@@ -35,9 +35,9 @@ from dns.exception import DNSException
import ldap
from nss.error import NSPRError
from ipapython import ipautil, sysrestore, admintool, dogtag
from ipapython import ipautil, sysrestore, admintool, dogtag, version
from ipapython.admintool import ScriptError
from ipapython.ipa_log_manager import *
from ipapython.ipa_log_manager import root_logger
from ipalib.util import validate_hostname
from ipapython import config
from ipalib import errors, x509
@@ -68,7 +68,7 @@ class HostnameLocalhost(HostLookupError):
pass
class ReplicaConfig:
def __init__(self):
def __init__(self, top_dir=None):
self.realm_name = ""
self.domain_name = ""
self.master_host_name = ""
@@ -78,6 +78,7 @@ class ReplicaConfig:
self.subject_base = None
self.setup_ca = False
self.version = 0
self.top_dir = top_dir
subject_base = ipautil.dn_attribute_property('_subject_base')
@@ -174,7 +175,7 @@ def verify_fqdn(host_name, no_host_dns=False, local_hostname=True):
raise HostReverseLookupError("The host name %s does not match the reverse lookup %s" % (host_name, revname))
verified.add(address)
def record_in_hosts(ip, host_name=None, file=paths.HOSTS):
def record_in_hosts(ip, host_name=None, conf_file=paths.HOSTS):
"""
Search record in /etc/hosts - static table lookup for hostnames
@@ -184,9 +185,9 @@ def record_in_hosts(ip, host_name=None, file=paths.HOSTS):
:param ip: IP address
:param host_name: Optional hostname to search
:param file: Optional path to the lookup table
:param conf_file: Optional path to the lookup table
"""
hosts = open(file, 'r').readlines()
hosts = open(conf_file, 'r').readlines()
for line in hosts:
line = line.rstrip('\n')
fields = line.partition('#')[0].split()
@@ -206,13 +207,13 @@ def record_in_hosts(ip, host_name=None, file=paths.HOSTS):
return None
return (hosts_ip, names)
except IndexError:
print "Warning: Erroneous line '%s' in %s" % (line, file)
print "Warning: Erroneous line '%s' in %s" % (line, conf_file)
continue
return None
def add_record_to_hosts(ip, host_name, file=paths.HOSTS):
hosts_fd = open(file, 'r+')
def add_record_to_hosts(ip, host_name, conf_file=paths.HOSTS):
hosts_fd = open(conf_file, 'r+')
hosts_fd.seek(0, 2)
hosts_fd.write(ip+'\t'+host_name+' '+host_name.split('.')[0]+'\n')
hosts_fd.close()
@@ -512,20 +513,20 @@ def expand_replica_info(filename, password):
"""
top_dir = tempfile.mkdtemp("ipa")
tarfile = top_dir+"/files.tar"
dir = top_dir + "/realm_info"
dir_path = top_dir + "/realm_info"
ipautil.decrypt_file(filename, tarfile, password, top_dir)
ipautil.run(["tar", "xf", tarfile, "-C", top_dir])
os.remove(tarfile)
return top_dir, dir
return top_dir, dir_path
def read_replica_info(dir, rconfig):
def read_replica_info(dir_path, rconfig):
"""
Read the contents of a replica installation file.
rconfig is a ReplicaConfig object
"""
filename = dir + "/realm_info"
filename = dir_path + "/realm_info"
fd = open(filename)
config = SafeConfigParser()
config.readfp(fd)
@@ -556,6 +557,67 @@ def read_replica_info_dogtag_port(config_dir):
return dogtag_master_ds_port
def read_replica_info_kra_enabled(config_dir):
"""
Check the replica info to determine if a KRA has been installed
on the master
"""
default_file = config_dir + "/default.conf"
if not ipautil.file_exists(default_file):
return False
else:
with open(default_file) as fd:
config = SafeConfigParser()
config.readfp(fd)
enable_kra = bool(config.get("global", "enable_kra"))
return enable_kra
def create_replica_config(dirman_password, filename, options):
top_dir = None
try:
top_dir, dir = expand_replica_info(filename, dirman_password)
except Exception, e:
root_logger.error("Failed to decrypt or open the replica file.")
print "ERROR: Failed to decrypt or open the replica file."
print "Verify you entered the correct Directory Manager password."
sys.exit(1)
config = ReplicaConfig(top_dir)
read_replica_info(dir, config)
root_logger.debug(
'Installing replica file with version %d (0 means no version in prepared file).',
config.version)
if config.version and config.version > version.NUM_VERSION:
root_logger.error(
'A replica file from a newer release (%d) cannot be installed on an older version (%d)',
config.version, version.NUM_VERSION)
sys.exit(1)
config.dirman_password = dirman_password
try:
host = get_host_name(options.no_host_dns)
except BadHostError, e:
root_logger.error(str(e))
sys.exit(1)
if config.host_name != host:
try:
print "This replica was created for '%s' but this machine is named '%s'" % (config.host_name, host)
if not ipautil.user_input("This may cause problems. Continue?", False):
root_logger.debug(
"Replica was created for %s but machine is named %s "
"User chose to exit",
config.host_name, host)
sys.exit(0)
config.host_name = host
print ""
except KeyboardInterrupt:
root_logger.debug("Keyboard Interrupt")
sys.exit(0)
config.dir = dir
config.ca_ds_port = read_replica_info_dogtag_port(config.dir)
return config
def check_server_configuration():
"""
Check if IPA server is configured on the system.
@@ -572,6 +634,7 @@ def check_server_configuration():
if not server_fstore.has_files():
raise RuntimeError("IPA is not configured on this system.")
def remove_file(filename):
"""
Remove a file and log any exceptions raised.
@@ -582,6 +645,7 @@ def remove_file(filename):
except Exception, e:
root_logger.error('Error removing %s: %s' % (filename, str(e)))
def rmtree(path):
"""
Remove a directory structure and log any exceptions raised.
@@ -592,6 +656,7 @@ def rmtree(path):
except Exception, e:
root_logger.error('Error removing %s: %s' % (path, str(e)))
def is_ipa_configured():
"""
Using the state and index install files determine if IPA is already
@@ -764,7 +829,7 @@ def check_pkcs12(pkcs12_info, ca_file, hostname):
raise ScriptError(
'%s server certificates found in %s, expecting only one' %
(len(server_certs), pkcs12_filename))
[(server_cert_name, server_cert_trust)] = server_certs
[(server_cert_name, _server_cert_trust)] = server_certs
# Check we have the whole cert chain & the CA is in it
trust_chain = nssdb.get_trust_chain(server_cert_name)
@@ -849,23 +914,23 @@ def stopped_service(service, instance_name=""):
root_logger.debug('Starting %s%s.', service, log_instance_name)
services.knownservices[service].start(instance_name)
def check_entropy():
'''
"""
Checks if the system has enough entropy, if not, displays warning message
'''
"""
try:
with open('/proc/sys/kernel/random/entropy_avail', 'r') as efname:
with open(paths.ENTROPY_AVAIL, 'r') as efname:
if int(efname.read()) < 200:
emsg = 'WARNING: Your system is running out of entropy, ' \
'you may experience long delays'
service.print_msg(emsg)
root_logger.debug(emsg)
except IOError as e:
root_logger.debug("Could not open /proc/sys/kernel/random/entropy_avail: %s" % \
e)
root_logger.debug(
"Could not open %s: %s", paths.ENTROPY_AVAIL, e)
except ValueError as e:
root_logger.debug("Invalid value in /proc/sys/kernel/random/entropy_avail %s" % \
e)
root_logger.debug("Invalid value in %s %s", paths.ENTROPY_AVAIL, e)
def validate_external_cert(cert_file, ca_file, subject_base):
extcert = None

View File

@@ -157,7 +157,7 @@ class Backup(admintool.AdminTool):
paths.NTP_CONF,
paths.SMB_CONF,
paths.SAMBA_KEYTAB,
paths.CA_AGENT_P12,
paths.DOGTAG_AGENT_P12,
paths.CACERT_P12,
paths.KRB5KDC_KDC_CONF,
paths.SYSTEMD_IPA_SERVICE,

View File

@@ -0,0 +1,243 @@
#! /usr/bin/python2 -E
# Authors: Ade Lee <alee@redhat.com>
#
# Copyright (C) 2014 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 <http://www.gnu.org/licenses/>.
#
from ConfigParser import RawConfigParser
from textwrap import dedent
from ipalib import api
from ipaplatform import services
from ipaplatform.paths import paths
from ipapython import admintool
from ipapython import dogtag
from ipapython import ipautil
from ipaserver.install import cainstance
from ipaserver.install import dogtaginstance
from ipaserver.install import krainstance
from ipaserver.install import dsinstance
from ipaserver.install import installutils
from ipaserver.install import service
from ipaserver.install.installutils import (
read_replica_info_kra_enabled, create_replica_config)
class KRAInstall(admintool.AdminTool):
command_name = 'ipa-kra-install'
usage = "%prog [options] [replica_file]"
description = "Install a master or replica KRA."
@classmethod
def add_options(cls, parser, debug_option=True):
super(KRAInstall, cls).add_options(parser, debug_option=True)
parser.add_option(
"-p", "--password",
dest="password", sensitive=True,
help="Directory Manager (existing master) password")
parser.add_option(
"-U", "--unattended",
dest="unattended", action="store_true", default=False,
help="unattended installation never prompts the user")
parser.add_option(
"--uninstall",
dest="uninstall", action="store_true", default=False,
help="uninstall an existing installation. The uninstall can "
"be run with --unattended option")
def validate_options(self, needs_root=True):
super(KRAInstall, self).validate_options(needs_root=True)
installutils.check_server_configuration()
if self.options.unattended and self.options.password is None:
self.option_parser.error(
"Directory Manager password must be specified using -p"
" in unattended mode"
)
api.bootstrap(in_server=True)
api.finalize()
def ask_for_options(self):
super(KRAInstall, self).ask_for_options()
if not self.options.password:
self.options.password = installutils.read_password(
"Directory Manager", confirm=False,
validate=False, retry=False)
if self.options.password is None:
raise admintool.ScriptError(
"Directory Manager password required")
@classmethod
def get_command_class(cls, options, args):
if options.uninstall:
return KRAUninstaller
else:
return KRAInstaller
class KRAUninstaller(KRAInstall):
log_file_name = paths.PKI_KRA_UNINSTALL_LOG
def validate_options(self, needs_root=True):
super(KRAUninstaller, self).validate_options(needs_root=True)
if self.args:
self.option_parser.error("Too many parameters provided.")
if not api.env.enable_kra:
self.option_parser.error(
"Cannot uninstall. There is no KRA installed on this system."
)
def run(self):
super(KRAUninstaller, self).run()
dogtag_constants = dogtag.configured_constants()
# temporarily disable uninstall until Dogtag ticket:
# https://fedorahosted.org/pki/ticket/1113 is fixed
# TODO(alee) remove this once the above ticket is fixed
raise admintool.ScriptError(
"Uninstall is temporarily disabled. To uninstall, please "
"use ipa-server-install --uninstall"
)
kra_instance = krainstance.KRAInstance(
api.env.realm, dogtag_constants=dogtag_constants)
kra_instance.stop_tracking_certificates(dogtag_constants)
if kra_instance.is_installed():
kra_instance.uninstall()
# Update config file
parser = RawConfigParser()
parser.read(paths.IPA_DEFAULT_CONF)
parser.set('global', 'enable_kra', 'False')
with open(paths.IPA_DEFAULT_CONF, 'w') as f:
parser.write(f)
class KRAInstaller(KRAInstall):
log_file_name = paths.PKI_KRA_INSTALL_LOG
INSTALLER_START_MESSAGE = '''
===================================================================
This program will setup Dogtag KRA for the FreeIPA Server.
'''
FAIL_MESSAGE = '''
Your system may be partly configured.
Run ipa-kra-install --uninstall to clean up.
'''
def validate_options(self, needs_root=True):
super(KRAInstaller, self).validate_options(needs_root=True)
dogtag_version = int(api.env.dogtag_version)
enable_kra = api.env.enable_kra
if enable_kra:
self.option_parser.error("KRA is already installed.")
ca_installed = cainstance.is_ca_installed_locally()
if ca_installed:
if dogtag_version >= 10:
# correct dogtag version of CA installed
pass
else:
self.option_parser.error(
"Dogtag must be version 10.2 or above to install KRA")
else:
self.option_parser.error(
"Dogtag CA is not installed. Please install the CA first")
self.installing_replica = dogtaginstance.is_installing_replica("KRA")
if self.installing_replica:
if not self.args:
self.option_parser.error("A replica file is required.")
if len(self.args) > 1:
self.option_parser.error("Too many arguments provided")
self.replica_file = self.args[0]
if not ipautil.file_exists(self.replica_file):
self.option_parser.error(
"Replica file %s does not exist" % self.replica_file)
else:
if self.args:
self.option_parser.error("Too many parameters provided. "
"No replica file is required.")
def _run(self):
super(KRAInstaller, self).run()
print dedent(self.INSTALLER_START_MESSAGE)
subject = dsinstance.DsInstance().find_subject_base()
if not self.installing_replica:
kra = krainstance.KRAInstance(
api.env.realm,
dogtag_constants=dogtag.install_constants)
kra.configure_instance(
api.env.host, api.env.domain, self.options.password,
self.options.password, subject_base=subject)
else:
replica_config = create_replica_config(
self.options.password,
self.replica_file,
self.options)
if not read_replica_info_kra_enabled(replica_config.dir):
raise admintool.ScriptError(
"Either KRA is not installed on the master system or "
"your replica file is out of date"
)
kra = krainstance.install_replica_kra(replica_config)
service.print_msg("Restarting the directory server")
ds = dsinstance.DsInstance()
ds.restart()
kra.enable_client_auth_to_db(kra.dogtag_constants.KRA_CS_CFG_PATH)
# Restart apache for new proxy config file
services.knownservices.httpd.restart(capture_output=True)
# Update config file
parser = RawConfigParser()
parser.read(paths.IPA_DEFAULT_CONF)
parser.set('global', 'enable_kra', 'True')
with open(paths.IPA_DEFAULT_CONF, 'w') as f:
parser.write(f)
def run(self):
try:
self._run()
except:
self.log.error(dedent(self.FAIL_MESSAGE))
raise

View File

@@ -371,6 +371,7 @@ class ReplicaPrepare(admintool.AdminTool):
cacert_filename = paths.CACERT_PEM
if ipautil.file_exists(cacert_filename):
self.copy_info_file(cacert_filename, "cacert.pem")
self.copy_info_file(paths.IPA_DEFAULT_CONF, "default.conf")
def save_config(self):
self.log.info("Finalizing configuration")

View File

@@ -0,0 +1,346 @@
# Authors: Ade Lee <alee@redhat.com>
#
# Copyright (C) 2014 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 <http://www.gnu.org/licenses/>.
#
import ConfigParser
import os
import pwd
import shutil
import sys
import tempfile
from ipalib import api
from ipaplatform import services
from ipaplatform.paths import paths
from ipapython import dogtag
from ipapython import ipaldap
from ipapython import ipautil
from ipapython.dn import DN
from ipaserver.install import certs
from ipaserver.install import cainstance
from ipaserver.install import service
from ipaserver.install.dogtaginstance import DogtagInstance
from ipaserver.install.dogtaginstance import DEFAULT_DSPORT, PKI_USER
from ipapython.ipa_log_manager import log_mgr
# When IPA is installed with DNS support, this CNAME should hold all IPA
# replicas with KRA configured
IPA_KRA_RECORD = "ipa-kra"
class KRAInstance(DogtagInstance):
"""
We assume that the CA has already been installed, and we use the
same tomcat instance to host both the CA and KRA.
The mod_nss database will contain the RA agent cert that will be used
to do authenticated requests against dogtag. The RA agent cert will
be the same for both the CA and KRA.
"""
def __init__(self, realm, dogtag_constants=None):
if dogtag_constants is None:
dogtag_constants = dogtag.configured_constants()
super(KRAInstance, self).__init__(
realm=realm,
subsystem="KRA",
service_desc="KRA server",
dogtag_constants=dogtag_constants
)
self.basedn = DN(('o', 'kra'), ('o', 'ipaca'))
self.tracking_reqs = (('auditSigningCert cert-pki-kra', None),
('transportCert cert-pki-kra', None),
('storageCert cert-pki-kra', None))
self.log = log_mgr.get_logger(self)
def configure_instance(self, host_name, domain, dm_password,
admin_password, ds_port=DEFAULT_DSPORT,
pkcs12_info=None, master_host=None,
master_replication_port=None,
subject_base=None):
"""Create a KRA instance.
To create a clone, pass in pkcs12_info.
"""
self.fqdn = host_name
self.domain = domain
self.dm_password = dm_password
self.admin_password = admin_password
self.ds_port = ds_port
self.pkcs12_info = pkcs12_info
if self.pkcs12_info is not None:
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
# Confirm that a KRA does not already exist
if self.is_installed():
raise RuntimeError(
"KRA already installed.")
# Confirm that a Dogtag 10 CA instance already exists
ca = cainstance.CAInstance(
api.env.realm, certs.NSS_DIR,
dogtag_constants=dogtag.Dogtag10Constants)
if not ca.is_installed():
raise RuntimeError(
"KRA configuration failed. "
"A Dogtag CA must be installed first")
self.step("configuring KRA instance", self.__spawn_instance)
if not self.clone:
self.step("add RA user to KRA agent group",
self.__add_ra_user_to_agent_group)
self.step("restarting KRA", self.restart_instance)
self.step("configure certificate renewals", self.configure_renewal)
self.step("Configure HTTP to proxy connections",
self.http_proxy)
self.start_creation(runtime=126)
def __spawn_instance(self):
"""
Create and configure a new KRA instance using pkispawn.
Creates a configuration file with IPA-specific
parameters and passes it to the base class to call pkispawn
"""
# Create an empty and secured file
(cfg_fd, cfg_file) = tempfile.mkstemp()
os.close(cfg_fd)
pent = pwd.getpwnam(PKI_USER)
os.chown(cfg_file, pent.pw_uid, pent.pw_gid)
# Create KRA configuration
config = ConfigParser.ConfigParser()
config.optionxform = str
config.add_section("KRA")
# Security Domain Authentication
config.set("KRA", "pki_security_domain_https_port", "443")
config.set("KRA", "pki_security_domain_password", self.admin_password)
config.set("KRA", "pki_security_domain_user", "admin")
# issuing ca
config.set("KRA", "pki_issuing_ca_uri", "https://%s" %
ipautil.format_netloc(self.fqdn, 443))
# Server
config.set("KRA", "pki_enable_proxy", "True")
config.set("KRA", "pki_restart_configured_instance", "False")
config.set("KRA", "pki_backup_keys", "True")
config.set("KRA", "pki_backup_password", self.admin_password)
# Client security database
config.set("KRA", "pki_client_database_dir", self.agent_db)
config.set("KRA", "pki_client_database_password", self.admin_password)
config.set("KRA", "pki_client_database_purge", "False")
config.set("KRA", "pki_client_pkcs12_password", self.admin_password)
# Administrator
config.set("KRA", "pki_admin_name", "admin")
config.set("KRA", "pki_admin_uid", "admin")
config.set("KRA", "pki_admin_email", "root@localhost")
config.set("KRA", "pki_admin_password", self.admin_password)
config.set("KRA", "pki_admin_nickname", "ipa-ca-agent")
config.set("KRA", "pki_admin_subject_dn",
str(DN(('cn', 'ipa-ca-agent'), self.subject_base)))
config.set("KRA", "pki_import_admin_cert", "True")
config.set("KRA", "pki_admin_cert_file", paths.ADMIN_CERT_PATH)
config.set("KRA", "pki_client_admin_cert_p12", paths.DOGTAG_AGENT_P12)
# Directory server
config.set("KRA", "pki_ds_ldap_port", str(self.ds_port))
config.set("KRA", "pki_ds_password", self.dm_password)
config.set("KRA", "pki_ds_base_dn", self.basedn)
config.set("KRA", "pki_ds_database", "ipaca")
config.set("KRA", "pki_ds_create_new_db", "False")
# Certificate subject DNs
config.set("KRA", "pki_subsystem_subject_dn",
str(DN(('cn', 'CA Subsystem'), self.subject_base)))
config.set("KRA", "pki_ssl_server_subject_dn",
str(DN(('cn', self.fqdn), self.subject_base)))
config.set("KRA", "pki_audit_signing_subject_dn",
str(DN(('cn', 'KRA Audit'), self.subject_base)))
config.set(
"KRA", "pki_transport_subject_dn",
str(DN(('cn', 'KRA Transport Certificate'), self.subject_base)))
config.set(
"KRA", "pki_storage_subject_dn",
str(DN(('cn', 'KRA Storage Certificate'), self.subject_base)))
# Certificate nicknames
# Note that both the server certs and subsystem certs reuse
# the ca certs.
config.set("KRA", "pki_subsystem_nickname",
"subsystemCert cert-pki-ca")
config.set("KRA", "pki_ssl_server_nickname",
"Server-Cert cert-pki-ca")
config.set("KRA", "pki_audit_signing_nickname",
"auditSigningCert cert-pki-kra")
config.set("KRA", "pki_transport_nickname",
"transportCert cert-pki-kra")
config.set("KRA", "pki_storage_nickname",
"storageCert cert-pki-kra")
# Shared db settings
# Needed because CA and KRA share the same database
# We will use the dbuser created for the CA
config.set("KRA", "pki_share_db", "True")
config.set(
"KRA", "pki_share_dbuser_dn",
str(DN(('uid', 'pkidbuser'), ('ou', 'people'), ('o', 'ipaca'))))
_p12_tmpfile_handle, p12_tmpfile_name = tempfile.mkstemp(dir=paths.TMP)
if self.clone:
krafile = self.pkcs12_info[0]
shutil.copy(krafile, p12_tmpfile_name)
pent = pwd.getpwnam(PKI_USER)
os.chown(p12_tmpfile_name, pent.pw_uid, pent.pw_gid)
# create admin cert file if it does not exist
cert = DogtagInstance.get_admin_cert(self)
with open(paths.ADMIN_CERT_PATH, "w") as admin_path:
admin_path.write(cert)
# Security domain registration
config.set("KRA", "pki_security_domain_hostname", self.master_host)
config.set("KRA", "pki_security_domain_https_port", "443")
config.set("KRA", "pki_security_domain_user", "admin")
config.set("KRA", "pki_security_domain_password",
self.admin_password)
# Clone
config.set("KRA", "pki_clone", "True")
config.set("KRA", "pki_clone_pkcs12_path", p12_tmpfile_name)
config.set("KRA", "pki_clone_pkcs12_password", self.dm_password)
config.set("KRA", "pki_clone_setup_replication", "False")
config.set(
"KRA", "pki_clone_uri",
"https://%s" % ipautil.format_netloc(self.master_host, 443))
# Generate configuration file
with open(cfg_file, "wb") as f:
config.write(f)
try:
DogtagInstance.spawn_instance(self, cfg_file)
finally:
os.remove(p12_tmpfile_name)
os.remove(cfg_file)
shutil.move(paths.KRA_BACKUP_KEYS_P12, paths.KRACERT_P12)
self.log.debug("completed creating KRA instance")
def __add_ra_user_to_agent_group(self):
"""
Add RA agent created for CA to KRA agent group.
"""
conn = ipaldap.IPAdmin(self.fqdn, self.ds_port)
conn.do_simple_bind(DN(('cn', 'Directory Manager')), self.dm_password)
entry_dn = DN(('uid', "ipara"), ('ou', 'People'), ('o', 'ipaca'))
dn = DN(('cn', 'Data Recovery Manager Agents'), ('ou', 'groups'),
self.basedn)
modlist = [(0, 'uniqueMember', '%s' % entry_dn)]
conn.modify_s(dn, modlist)
conn.unbind()
@staticmethod
def update_cert_config(nickname, cert, dogtag_constants=None):
"""
When renewing a KRA subsystem certificate the configuration file
needs to get the new certificate as well.
nickname is one of the known nicknames.
cert is a DER-encoded certificate.
"""
if dogtag_constants is None:
dogtag_constants = dogtag.configured_constants()
# The cert directive to update per nickname
directives = {
'auditSigningCert cert-pki-kra': 'kra.audit_signing.cert',
'storageCert cert-pki-kra': 'kra.storage.cert',
'transportCert cert-pki-kra': 'kra.transport.cert',
'subsystemCert cert-pki-kra': 'kra.subsystem.cert',
'Server-Cert cert-pki-ca': 'kra.sslserver.cert'}
DogtagInstance.update_cert_cs_cfg(
nickname, cert, directives,
dogtag.configured_constants().KRA_CS_CFG_PATH,
dogtag_constants)
def install_replica_kra(config, postinstall=False):
"""
Install a KRA on a replica.
There are two modes of doing this controlled:
- While the replica is being installed
- Post-replica installation
config is a ReplicaConfig object
Returns a KRA instance
"""
# note that the cacert.p12 file is regenerated during the
# ipa-replica-prepare process and should include all the certs
# for the CA and KRA
krafile = config.dir + "/cacert.p12"
if not ipautil.file_exists(krafile):
raise RuntimeError(
"Unable to clone KRA."
" cacert.p12 file not found in replica file")
_kra = KRAInstance(config.realm_name,
dogtag_constants=dogtag.install_constants)
_kra.dm_password = config.dirman_password
_kra.subject_base = config.subject_base
if _kra.is_installed():
sys.exit("A KRA is already configured on this system.")
_kra.configure_instance(config.host_name, config.domain_name,
config.dirman_password, config.dirman_password,
pkcs12_info=(krafile,),
master_host=config.master_host_name,
master_replication_port=config.ca_ds_port,
subject_base=config.subject_base)
# Restart httpd since we changed it's config and added ipa-pki-proxy.conf
if postinstall:
services.knownservices.httpd.restart()
# The dogtag DS instance needs to be restarted after installation.
# The procedure for this is: stop dogtag, stop DS, start DS, start
# dogtag
service.print_msg("Restarting the directory and KRA servers")
_kra.stop(dogtag.install_constants.PKI_INSTANCE_NAME)
services.knownservices.dirsrv.restart()
_kra.start(dogtag.install_constants.PKI_INSTANCE_NAME)
return _kra

View File

@@ -1,10 +1,11 @@
# Authors:
# Ade Lee <alee@redhat.com>
# Andrew Wnuk <awnuk@redhat.com>
# Jason Gerard DeRose <jderose@redhat.com>
# Rob Crittenden <rcritten@@redhat.com>
# John Dennis <jdennis@redhat.com>
#
# Copyright (C) 2009 Red Hat
# Copyright (C) 2014 Red Hat
# see file 'COPYING' for use and warranty information
#
# This program is free software; you can redistribute it and/or modify
@@ -34,7 +35,7 @@ variety of names, the open source version is called "dogtag".
CMS consists of a number of servlets which in rough terms can be thought of as
RPC commands. A servlet is invoked by making an HTTP request to a specific URL
and passing URL arguments. Normally CMS responds with an HTTP reponse consisting
and passing URL arguments. Normally CMS responds with an HTTP response consisting
of HTML to be rendered by a web browser. This HTTP HTML response has both
Javascript SCRIPT components and HTML rendering code. One of the Javascript
SCRIPT blocks holds the data for the result. The rest of the response is derived
@@ -42,13 +43,13 @@ from templates associated with the servlet which may be customized. The
templates pull the result data from Javascript variables.
One way to get the result data is to parse the HTML looking for the Javascript
varible initializations. Simple string searchs are not a robust method. First of
variable initializations. Simple string searches are not a robust method. First of
all one must be sure the string is only found in a Javascript SCRIPT block and
not somewhere else in the HTML document. Some of the Javascript variable
initializations are rather complex (e.g. lists of structures). It would be hard
to correctly parse such complex and diverse Javascript. Existing Javascript
parsers are not generally available. Finally, it's important to know the
character encoding for strings. There is a somewhat complex set of precident
character encoding for strings. There is a somewhat complex set of precedent
rules for determining the current character encoding from the HTTP header,
meta-equiv tags, mime Content-Type and charset attributes on HTML elements. All
of this means trying to read the result data from a CMS HTML response is
@@ -119,7 +120,7 @@ values. Python also nicely handles type promotion transparently between int
and long objects. For example if you multiply two int objects you may get back
a long object if necessary. In general Python int and long objects may be
freely mixed without the programmer needing to be aware of which type of
intergral object is being operated on.
integral object is being operated on.
The leads to the following rule, always parse a string representing an
integral value using the int() constructor even if it might have large
@@ -229,20 +230,28 @@ as a dict via the 'namespaces' keyword parameter of etree.XPath(). The predicate
for the second location step uses the 're:' namespace to find the function name
'match'. The re:match() takes a string to search as its first argument and a
regular expression pattern as its second argument. In this example the string
to seach is the node name of the location step because we called the built-in
to search is the node name of the location step because we called the built-in
node() function of XPath. The regular expression pattern we've passed says it's
a match if the string begins with 'chapter' is followed by any number of
digits and nothing else follows.
'''
from lxml import etree
import urllib2
import datetime
from lxml import etree
import tempfile
import time
import urllib2
from pki.client import PKIConnection
import pki.crypto as cryptoutil
from pki.kra import KRAClient
from ipalib import Backend
from ipapython.dn import DN
import ipapython.dogtag
from ipapython import ipautil
from ipaserver.install.certs import CertDB
# These are general status return values used when
# CMSServlet.outputError() is invoked.
@@ -260,6 +269,7 @@ CMS_STATUS_REJECTED = 5
CMS_STATUS_ERROR = 6
CMS_STATUS_EXCEPTION = 7
def cms_request_status_to_string(request_status):
'''
:param request_status: The integral request status value
@@ -290,7 +300,7 @@ def parse_and_set_boolean_xml(node, response, response_name):
'''
:param node: xml node object containing value to parse for boolean result
:param response: response dict to set boolean result in
:param response_name: name of the respone value to set
:param response_name: name of the response value to set
:except ValueError:
Read the value out of a xml text node and interpret it as a boolean value.
@@ -646,7 +656,7 @@ def parse_check_request_result_xml(doc):
+-------------------------+---------------+-------------------+-----------------+
|requestId |string |request_id |string |
+-------------------------+---------------+-------------------+-----------------+
|staus |string |cert_request_status|unicode [1]_ |
|status |string |cert_request_status|unicode [1]_ |
+-------------------------+---------------+-------------------+-----------------+
|createdOn |long, timestamp|created_on |datetime.datetime|
+-------------------------+---------------+-------------------+-----------------+
@@ -1199,6 +1209,57 @@ def parse_unrevoke_cert_xml(doc):
return response
def host_has_service(host, ldap2, service='CA'):
"""
:param host: A host which might be a master for a service.
:param ldap2: connection to the local database
:param service: The service for which the host might be a master.
:return: (true, false)
Check if a specified host is a master for a specified service.
"""
base_dn = DN(('cn', host), ('cn', 'masters'), ('cn', 'ipa'),
('cn', 'etc'), api.env.basedn)
filter_attrs = {
'objectClass': 'ipaConfigObject',
'cn': service,
'ipaConfigString': 'enabledService',
}
query_filter = ldap2.make_filter(filter_attrs, rules='&')
try:
ent, trunc = ldap2.find_entries(filter=query_filter, base_dn=base_dn)
if len(ent):
return True
except Exception:
pass
return False
def select_any_master(ldap2, service='CA'):
"""
:param ldap2: connection to the local database
:param service: The service for which we're looking for a master.
:return: host as str
Select any host which is a master for a specified service.
"""
base_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'),
api.env.basedn)
filter_attrs = {
'objectClass': 'ipaConfigObject',
'cn': service,
'ipaConfigString': 'enabledService',}
query_filter = ldap2.make_filter(filter_attrs, rules='&')
try:
ent, trunc = ldap2.find_entries(filter=query_filter, base_dn=base_dn)
if len(ent):
entry = random.choice(ent)
return entry.dn[1].value
except Exception:
pass
return None
#-------------------------------------------------------------------------------
from ipalib import api, SkipPluginModule
@@ -1214,6 +1275,7 @@ from ipapython import dogtag
from ipalib import _
from ipaplatform.paths import paths
class ra(rabase.rabase):
"""
Request Authority backend plugin.
@@ -1258,57 +1320,6 @@ class ra(rabase.rabase):
self.error('%s.%s(): %s', self.fullname, func_name, err_msg)
raise CertificateOperationError(error=err_msg)
def _host_has_service(self, host, service='CA'):
"""
:param host: A host which might be a master for a service.
:param service: The service for which the host might be a master.
:return: (true, false)
Check if a specified host is a master for a specified service.
"""
ldap2 = self.api.Backend.ldap2
base_dn = DN(('cn', host), ('cn', 'masters'), ('cn', 'ipa'),
('cn', 'etc'), api.env.basedn)
filter_attrs = {
'objectClass': 'ipaConfigObject',
'cn': service,
'ipaConfigString': 'enabledService',
}
filter = ldap2.make_filter(filter_attrs, rules='&')
try:
ent, trunc = ldap2.find_entries(filter=filter, base_dn=base_dn)
if len(ent):
return True
except Exception, e:
pass
return False
def _select_any_master(self, service='CA'):
"""
:param service: The service for which we're looking for a master.
:return: host
as str
Select any host which is a master for a specified service.
"""
ldap2 = self.api.Backend.ldap2
base_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'),
api.env.basedn)
filter_attrs = {
'objectClass': 'ipaConfigObject',
'cn': service,
'ipaConfigString': 'enabledService',
}
filter = ldap2.make_filter(filter_attrs, rules='&')
try:
ent, trunc = ldap2.find_entries(filter=filter, base_dn=base_dn)
if len(ent):
entry = random.choice(ent)
return entry.dn[1].value
except Exception, e:
pass
return None
@cachedproperty
def ca_host(self):
"""
@@ -1317,12 +1328,13 @@ class ra(rabase.rabase):
Select our CA host.
"""
if self._host_has_service(host=api.env.ca_host):
ldap2 = self.api.Backend.ldap2
if host_has_service(api.env.ca_host, ldap2, "CA"):
return api.env.ca_host
if api.env.host != api.env.ca_host:
if self._host_has_service(host=api.env.host):
if host_has_service(api.env.host, ldap2, "CA"):
return api.env.host
host = self._select_any_master()
host = select_any_master(ldap2)
if host:
return host
else:
@@ -1363,7 +1375,8 @@ class ra(rabase.rabase):
parser = etree.XMLParser()
doc = etree.fromstring(xml_text, parser)
result = parse_func(doc)
self.debug("%s() xml_text:\n%s\nparse_result:\n%s" % (parse_func.__name__, xml_text, result))
self.debug("%s() xml_text:\n%s\n"
"parse_result:\n%s" % (parse_func.__name__, xml_text, result))
return result
def check_request_status(self, request_id):
@@ -1410,7 +1423,7 @@ class ra(rabase.rabase):
xml='true')
# Parse and handle errors
if (http_status != 200):
if http_status != 200:
self.raise_certificate_operation_error('check_request_status',
detail=http_reason_phrase)
@@ -1440,10 +1453,10 @@ class ra(rabase.rabase):
Retrieve an existing certificate.
:param serial_number: Certificate serial number. Must be a string value
because serial numbers may be of any magnitue and
because serial numbers may be of any magnitude and
XMLRPC cannot handle integers larger than 64-bit.
The string value should be decimal, but may optionally
be prefixed with a hex radix prefix if the integal value
be prefixed with a hex radix prefix if the integral value
is represented as hexadecimal. If no radix prefix is
supplied the string will be interpreted as decimal.
@@ -1496,7 +1509,7 @@ class ra(rabase.rabase):
# Parse and handle errors
if (http_status != 200):
if http_status != 200:
self.raise_certificate_operation_error('get_certificate',
detail=http_reason_phrase)
@@ -1563,7 +1576,7 @@ class ra(rabase.rabase):
cert_request=csr,
xml='true')
# Parse and handle errors
if (http_status != 200):
if http_status != 200:
self.raise_certificate_operation_error('request_certificate',
detail=http_reason_phrase)
@@ -1604,10 +1617,10 @@ class ra(rabase.rabase):
def revoke_certificate(self, serial_number, revocation_reason=0):
"""
:param serial_number: Certificate serial number. Must be a string value
because serial numbers may be of any magnitue and
because serial numbers may be of any magnitude and
XMLRPC cannot handle integers larger than 64-bit.
The string value should be decimal, but may optionally
be prefixed with a hex radix prefix if the integal value
be prefixed with a hex radix prefix if the integral value
is represented as hexadecimal. If no radix prefix is
supplied the string will be interpreted as decimal.
:param revocation_reason: Integer code of revocation reason.
@@ -1644,7 +1657,7 @@ class ra(rabase.rabase):
xml='true')
# Parse and handle errors
if (http_status != 200):
if http_status != 200:
self.raise_certificate_operation_error('revoke_certificate',
detail=http_reason_phrase)
@@ -1668,10 +1681,10 @@ class ra(rabase.rabase):
def take_certificate_off_hold(self, serial_number):
"""
:param serial_number: Certificate serial number. Must be a string value
because serial numbers may be of any magnitue and
because serial numbers may be of any magnitude and
XMLRPC cannot handle integers larger than 64-bit.
The string value should be decimal, but may optionally
be prefixed with a hex radix prefix if the integal value
be prefixed with a hex radix prefix if the integral value
is represented as hexadecimal. If no radix prefix is
supplied the string will be interpreted as decimal.
@@ -1704,7 +1717,7 @@ class ra(rabase.rabase):
xml='true')
# Parse and handle errors
if (http_status != 200):
if http_status != 200:
self.raise_certificate_operation_error('take_certificate_off_hold',
detail=http_reason_phrase)
@@ -1866,4 +1879,133 @@ class ra(rabase.rabase):
return results
api.register(ra)
# ----------------------------------------------------------------------------
class kra(Backend):
"""
KRA backend plugin (for Vault)
"""
def __init__(self, kra_port=443):
if api.env.in_tree:
self.sec_dir = os.path.join(api.env.dot_ipa, 'alias')
pwd_file = os.path.join(self.sec_dir, '.pwd')
self.pem_file = os.path.join(self.sec_dir, ".pemfile")
else:
self.sec_dir = paths.HTTPD_ALIAS_DIR
pwd_file = paths.ALIAS_PWDFILE_TXT
self.pem_file = paths.DOGTAG_AGENT_PEM
self.kra_port = kra_port
self.transport_nick = "IPA KRA Transport Cert"
self.password = ""
with open(pwd_file, "r") as f:
self.password = f.readline().strip()
self.keyclient = None
super(kra, self).__init__()
def _create_pem_file(self):
""" Create PEM file used by KRA plugin for authentication.
This function reads the IPA HTTPD database and extracts the
Dogtag agent certificate and keys into a PKCS#12 temporary file.
The PKCS#12 file is then converted into PEM format so that it
can be used by python-requests to authenticate to the KRA.
:return: None
"""
(p12_pwd_fd, p12_pwd_fname) = tempfile.mkstemp()
(p12_fd, p12_fname) = tempfile.mkstemp()
try:
os.write(p12_pwd_fd, self.password)
os.close(p12_pwd_fd)
os.close(p12_fd)
certdb = CertDB(api.env.realm)
certdb.export_pkcs12(p12_fname, p12_pwd_fname, "ipaCert")
certdb.install_pem_from_p12(p12_fname, self.password, self.pem_file)
except:
self.debug("Error when creating PEM file for KRA operations")
raise
finally:
os.remove(p12_fname)
os.remove(p12_pwd_fname)
def _transport_cert_present(self):
""" Check if the client certDB contains the KRA transport certificate
:return: True/False
"""
# certutil -L -d db_dir -n cert_nick
certdb = CertDB(api.env.realm)
return certdb.has_nickname(self.transport_nick)
def _setup(self):
""" Do initial setup and crypto initialization of the KRA client
Creates a PEM file containing the KRA agent cert/keys to be used for
authentication to the KRA (if it does not already exist), Sets up a
connection to the KRA and initializes an NSS certificate database to
store the transport certificate, Retrieves the transport certificate
if it is not already present.
"""
#set up pem file if not present
if not os.path.exists(self.pem_file):
self._create_pem_file()
# set up connection
connection = PKIConnection('https',
self.kra_host,
str(self.kra_port),
'kra')
connection.set_authentication_cert(self.pem_file)
crypto = cryptoutil.NSSCryptoProvider(self.sec_dir, self.password)
#create kraclient
kraclient = KRAClient(connection, crypto)
# get transport cert if needed
if not self._transport_cert_present():
transport_cert = kraclient.system_certs.get_transport_cert()
crypto.import_cert(self.transport_nick, transport_cert, "u,u,u")
crypto.initialize()
self.keyclient = kraclient.keys
self.keyclient.set_transport_cert(self.transport_nick)
@cachedproperty
def kra_host(self):
"""
:return: host
as str
Select our KRA host.
"""
ldap2 = self.api.Backend.ldap2
if host_has_service(api.env.kra_host, ldap2, "kra"):
return api.env.kra_host
if api.env.host != api.env.kra_host:
if host_has_service(api.env.host, ldap2, "kra"):
return api.env.host
host = select_any_master(ldap2, "kra")
if host:
return host
else:
return api.env.kra_host
def get_keyclient(self):
"""Return a keyclient to perform key archival and retrieval.
:return: pki.key.keyclient
"""
if self.keyclient is None:
self._setup()
return self.keyclient
api.register(kra)