diff options
Diffstat (limited to 'ipaserver/install')
-rw-r--r-- | ipaserver/install/cainstance.py | 561 | ||||
-rw-r--r-- | ipaserver/install/dogtaginstance.py | 399 | ||||
-rw-r--r-- | ipaserver/install/dsinstance.py | 87 | ||||
-rw-r--r-- | ipaserver/install/installutils.py | 107 | ||||
-rw-r--r-- | ipaserver/install/ipa_backup.py | 2 | ||||
-rw-r--r-- | ipaserver/install/ipa_kra_install.py | 243 | ||||
-rw-r--r-- | ipaserver/install/ipa_replica_prepare.py | 1 | ||||
-rw-r--r-- | ipaserver/install/krainstance.py | 346 |
8 files changed, 1375 insertions, 371 deletions
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py index 2a8ecc00c..e8bb7d701 100644 --- a/ipaserver/install/cainstance.py +++ b/ipaserver/install/cainstance.py @@ -19,53 +19,51 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # -import pwd +import array +import base64 +import binascii +import ConfigParser +import dbus +import httplib +import ldap import os -import sys +import pwd import re +import shutil +import stat +import subprocess +import sys +import syslog import time -import ldap -import base64 -import array import tempfile -import binascii -import shutil -import httplib import urllib import xml.dom.minidom -import stat -import syslog -import ConfigParser -import dbus -from ipapython import dogtag -from ipapython.certdb import get_ca_nickname -from ipapython import certmonger from ipalib import api from ipalib import pkcs10, x509 from ipalib import errors -from ipapython.dn import DN -import subprocess -import traceback +from ipaplatform import services +from ipaplatform.paths import paths +from ipaplatform.tasks import tasks + +from ipapython import dogtag +from ipapython import certmonger from ipapython import ipautil from ipapython import ipaldap -from ipaplatform.tasks import tasks -from ipaserver.install import service -from ipaserver.install import installutils -from ipaserver.install import dsinstance +from ipapython.certdb import get_ca_nickname +from ipapython.dn import DN +from ipapython.ipa_log_manager import log_mgr,\ + standard_logging_setup, root_logger + from ipaserver.install import certs -from ipaserver.install.installutils import stopped_service +from ipaserver.install import dsinstance +from ipaserver.install import installutils +from ipaserver.install import service +from ipaserver.install.dogtaginstance import DogtagInstance +from ipaserver.install.dogtaginstance import PKI_USER, DEFAULT_DSPORT from ipaserver.plugins import ldap2 -from ipapython.ipa_log_manager import * -from ipaplatform import services -from ipaplatform.paths import paths - -HTTPD_CONFD = paths.HTTPD_CONF_D_DIR -DEFAULT_DSPORT = dogtag.install_constants.DS_PORT -PKI_USER = "pkiuser" -PKI_DS_USER = dogtag.install_constants.DS_USER # When IPA is installed with DNS support, this CNAME should hold all IPA # replicas with CA configured @@ -88,22 +86,6 @@ RootDNPwd= $PASSWORD ConfigFile = /usr/share/pki/ca/conf/database.ldif """ -def check_inst(): - """ - 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 - - # This is the template tomcat file for a CA - if not os.path.exists(paths.PKI_CONF_SERVER_XML): - return False - - return True def get_preop_pin(instance_root, instance_name): # Only used for Dogtag 9 @@ -113,24 +95,27 @@ def get_preop_pin(instance_root, instance_name): # read the config file and get the preop pin try: - f=open(filename) + f = open(filename) except IOError, e: root_logger.error("Cannot open configuration file." + str(e)) raise e data = f.read() data = data.split('\n') - pattern = re.compile("preop.pin=(.*)" ) + pattern = re.compile("preop.pin=(.*)") for line in data: match = re.search(pattern, line) - if (match): - preop_pin=match.group(1) + if match: + preop_pin = match.group(1) break if preop_pin is None: - raise RuntimeError("Unable to find preop.pin in %s. Is your CA already configured?" % filename) + raise RuntimeError( + "Unable to find preop.pin in %s. Is your CA already configured?" % + filename) return preop_pin + def import_pkcs12(input_file, input_passwd, cert_database, cert_passwd): ipautil.run([paths.PK12UTIL, "-d", cert_database, @@ -138,21 +123,23 @@ def import_pkcs12(input_file, input_passwd, cert_database, "-k", cert_passwd, "-w", input_passwd]) + def get_value(s): """ Parse out a name/value pair from a Javascript variable. """ try: - expr = s.split('=',1) + expr = s.split('=', 1) value = expr[1] value = value.replace('\"', '') - value = value.replace(';','') - value = value.replace('\\n','\n') - value = value.replace('\\r','\r') + value = value.replace(';', '') + value = value.replace('\\n', '\n') + value = value.replace('\\r', '\r') return value except IndexError: return None + def find_substring(data, value): """ Scan through a list looking for a string that starts with value. @@ -161,6 +148,7 @@ def find_substring(data, value): if d.startswith(value): return get_value(d) + def get_defList(data): """ Return a dictionary of defList name/value pairs. @@ -196,6 +184,7 @@ def get_defList(data): return defdict + def get_outputList(data): """ Return a dictionary of outputList name/value pairs. @@ -221,6 +210,7 @@ def get_outputList(data): return outputdict + def get_crl_files(path=None): """ Traverse dogtag's CRL files in default CRL publish directory or in chosen @@ -240,8 +230,8 @@ def get_crl_files(path=None): def is_step_one_done(): - '''Read CS.cfg and determine if step one of an external CA install is done - ''' + """Read CS.cfg and determine if step one of an external CA install is done + """ path = dogtag.install_constants.CS_CFG_PATH if not os.path.exists(path): return False @@ -251,6 +241,14 @@ def is_step_one_done(): return False +def is_ca_installed_locally(): + """Check if CA is installed locally by checking for existence of CS.cfg + :return:True/False + """ + path = dogtag.install_constants.CS_CFG_PATH + return os.path.exists(path) + + class CADSInstance(service.Service): """Certificate Authority DS instance @@ -258,7 +256,8 @@ class CADSInstance(service.Service): Thus this class only does uninstallation. """ def __init__(self, host_name=None, realm_name=None, domain_name=None, dm_password=None, dogtag_constants=None): - service.Service.__init__(self, "pkids", + service.Service.__init__( + self, "pkids", service_desc="directory server for the CA", dm_password=dm_password, ldapi=False, @@ -284,7 +283,7 @@ class CADSInstance(service.Service): serverid = self.restore_state("serverid") # Just eat this state if it exists - running = self.restore_state("running") + self.restore_state("running") if not enabled is None and not enabled: services.knownservices.dirsrv.disable() @@ -297,41 +296,14 @@ class CADSInstance(service.Service): dsdb.untrack_server_cert("Server-Cert") dsinstance.erase_ds_instance_data(serverid) - user_exists = self.restore_state("user_exists") + self.restore_state("user_exists") # At one time we removed this user on uninstall. That can potentially - # orphan files, or worse, if another useradd runs in the intermim, + # orphan files, or worse, if another useradd runs in the interim, # cause files to have a new owner. -def stop_tracking_certificates(dogtag_constants): - """Stop tracking our certificates. Called on uninstall. - """ - cmonger = services.knownservices.certmonger - services.knownservices.messagebus.start() - cmonger.start() - - for nickname in ['Server-Cert cert-pki-ca', - 'auditSigningCert cert-pki-ca', - 'ocspSigningCert cert-pki-ca', - 'subsystemCert cert-pki-ca', - 'caSigningCert cert-pki-ca']: - try: - certmonger.stop_tracking( - dogtag_constants.ALIAS_DIR, nickname=nickname) - except (ipautil.CalledProcessError, RuntimeError), e: - root_logger.error( - "certmonger failed to stop tracking certificate: %s" % str(e)) - - try: - certmonger.stop_tracking(paths.HTTPD_ALIAS_DIR, nickname='ipaCert') - except (ipautil.CalledProcessError, RuntimeError), e: - root_logger.error( - "certmonger failed to stop tracking certificate: %s" % str(e)) - cmonger.stop() - - -class CAInstance(service.Service): +class CAInstance(DogtagInstance): """ When using a dogtag CA the DS database contains just the server cert for DS. The mod_nss database will contain the RA agent @@ -353,19 +325,15 @@ class CAInstance(service.Service): if dogtag_constants is None: dogtag_constants = dogtag.configured_constants() - service.Service.__init__(self, - '%sd' % dogtag_constants.PKI_INSTANCE_NAME, - service_desc="certificate server", - dm_password=dm_password, - ldapi=ldapi) - - self.dogtag_constants = dogtag_constants - self.realm = realm - self.admin_password = None - self.fqdn = host_name - self.domain = None - self.pkcs12_info = None - self.clone = False + super(CAInstance, self).__init__( + realm=realm, + subsystem="CA", + service_desc="certificate server", + dogtag_constants=dogtag_constants, + host_name=host_name, + dm_password=dm_password, + ldapi=ldapi + ) # for external CAs self.external = 0 @@ -374,36 +342,17 @@ class CAInstance(service.Service): self.cert_chain_file = None self.create_ra_agent_db = True - # The same database is used for mod_nss because the NSS context - # will already have been initialized by Apache by the time - # mod_python wants to do things. - self.canickname = None - if realm: - self.canickname = get_ca_nickname(realm) - self.basedn = DN(('o', 'ipaca')) - self.ca_agent_db = tempfile.mkdtemp(prefix = "tmp-") + self.canickname = get_ca_nickname(realm) self.ra_agent_db = ra_db - self.ra_agent_pwd = None - if ra_db: - self.ra_agent_pwd = ra_db + "/pwdfile.txt" - self.ds_port = DEFAULT_DSPORT - self.security_domain_name = "IPA" - self.server_root = dogtag_constants.SERVER_ROOT + self.ra_agent_pwd = self.ra_agent_db + "/pwdfile.txt" self.ra_cert = None self.requestId = None - - def __del__(self): - shutil.rmtree(self.ca_agent_db, ignore_errors=True) - - def is_installed(self): - """ - Installing with an external CA is a two-step process. This - is used to determine if the first step has been done. - - Returns True/False - """ - return os.path.exists(os.path.join( - self.server_root, self.dogtag_constants.PKI_INSTANCE_NAME)) + self.tracking_reqs = (('Server-Cert cert-pki-ca', None), + ('auditSigningCert cert-pki-ca', None), + ('ocspSigningCert cert-pki-ca', None), + ('subsystemCert cert-pki-ca', None), + ('caSigningCert cert-pki-ca', 'ipaCACertRenewal')) + self.log = log_mgr.get_logger(self) def configure_instance(self, host_name, domain, dm_password, admin_password, ds_port=DEFAULT_DSPORT, @@ -440,12 +389,12 @@ class CAInstance(service.Service): # Determine if we are installing as an externally-signed CA and # what stage we're in. if csr_file is not None: - self.csr_file=csr_file - self.external=1 + self.csr_file = csr_file + self.external = 1 elif cert_file is not None: - self.cert_file=cert_file - self.cert_chain_file=cert_chain_file - self.external=2 + self.cert_file = cert_file + self.cert_chain_file = cert_chain_file + self.external = 2 self.step("creating certificate server user", self.__create_ca_user) if self.dogtag_constants.DOGTAG_VERSION >= 10: @@ -454,11 +403,11 @@ class CAInstance(service.Service): if not ipautil.dir_exists(paths.VAR_LIB_PKI_CA_DIR): self.step("creating pki-ca instance", self.create_instance) self.step("configuring certificate server instance", self.__configure_instance) - self.step("stopping certificate server instance to update CS.cfg", self.__stop) + self.step("stopping certificate server instance to update CS.cfg", self.stop_instance) self.step("disabling nonces", self.__disable_nonce) self.step("set up CRL publishing", self.__enable_crl_publish) self.step("enable PKIX certificate path discovery and validation", self.enable_pkix) - self.step("starting certificate server instance", self.__start) + self.step("starting certificate server instance", self.start_instance) # Step 1 of external is getting a CSR so we don't need to do these # steps until we get a cert back from the external CA. if self.external != 1: @@ -474,33 +423,28 @@ class CAInstance(service.Service): self.step("enabling Subject Alternative Name", self.enable_subject_alternative_name) self.step("enabling CRL and OCSP extensions for certificates", self.__set_crl_ocsp_extensions) self.step("setting audit signing renewal to 2 years", self.set_audit_renewal) - self.step("configuring certificate server to start on boot", self.__enable) + self.step("configuring certificate server to start on boot", + self.enable) if not self.clone: - self.step("restarting certificate server", self.__restart_instance) + self.step("restarting certificate server", self.restart_instance) self.step("requesting RA certificate from CA", self.__request_ra_certificate) self.step("issuing RA agent certificate", self.__issue_ra_cert) self.step("adding RA agent as a trusted user", self.__configure_ra) self.step("configure certmonger for renewals", self.configure_certmonger_renewal) - self.step("configure certificate renewals", self.configure_renewal) + self.step("configure certificate renewals", self.configure_cert_renewal) if not self.clone: self.step("configure RA certificate renewal", self.configure_agent_renewal) self.step("configure Server-Cert certificate renewal", self.track_servercert) - self.step("Configure HTTP to proxy connections", self.__http_proxy) + self.step("Configure HTTP to proxy connections", + self.http_proxy) self.start_creation(runtime=210) - def __stop(self): - self.stop() - - def __start(self): - self.start() - - def __spawn_instance(self): """ Create and configure a new CA instance using pkispawn. - pkispawn requires a configuration file with IPA-specific - parameters. + Creates the config file with IPA specific parameters + and passes it to the base class to call pkispawn """ # Create an empty and secured file @@ -522,7 +466,7 @@ class CAInstance(service.Service): config.set("CA", "pki_backup_password", self.admin_password) # Client security database - config.set("CA", "pki_client_database_dir", self.ca_agent_db) + config.set("CA", "pki_client_database_dir", self.agent_db) config.set("CA", "pki_client_database_password", self.admin_password) config.set("CA", "pki_client_database_purge", "False") config.set("CA", "pki_client_pkcs12_password", self.admin_password) @@ -535,7 +479,7 @@ class CAInstance(service.Service): config.set("CA", "pki_admin_nickname", "ipa-ca-agent") config.set("CA", "pki_admin_subject_dn", str(DN(('cn', 'ipa-ca-agent'), self.subject_base))) - config.set("CA", "pki_client_admin_cert_p12", paths.CA_AGENT_P12) + config.set("CA", "pki_client_admin_cert_p12", paths.DOGTAG_AGENT_P12) # Directory server config.set("CA", "pki_ds_ldap_port", str(self.ds_port)) @@ -562,7 +506,7 @@ class CAInstance(service.Service): config.set("CA", "pki_audit_signing_nickname", "auditSigningCert cert-pki-ca") config.set("CA", "pki_ca_signing_nickname", "caSigningCert cert-pki-ca") - if (self.clone): + if self.clone: cafile = self.pkcs12_info[0] shutil.copy(cafile, paths.TMP_CA_P12) pent = pwd.getpwnam(PKI_USER) @@ -610,22 +554,9 @@ class CAInstance(service.Service): with open(cfg_file, "wb") as f: config.write(f) - # Define the things we don't want logged - nolog = (self.admin_password, self.dm_password,) - - args = [paths.PKISPAWN, "-s", "CA", "-f", cfg_file ] - - with open(cfg_file) as f: - root_logger.debug( - 'Contents of pkispawn configuration file (%s):\n%s' % - (cfg_file, ipautil.nolog_replace(f.read(), nolog))) - self.backup_state('installed', True) try: - ipautil.run(args, nolog=nolog) - except ipautil.CalledProcessError, e: - root_logger.critical("failed to configure ca instance %s" % e) - raise RuntimeError('Configuration of CA failed') + DogtagInstance.spawn_instance(self, cfg_file) finally: os.remove(cfg_file) @@ -634,10 +565,10 @@ class CAInstance(service.Service): 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, \ + shutil.move(paths.CA_BACKUP_KEYS_P12, paths.CACERT_P12) - root_logger.debug("completed creating ca instance") + self.log.debug("completed creating ca instance") def create_instance(self): """ @@ -668,29 +599,21 @@ class CAInstance(service.Service): self.backup_state('installed', True) ipautil.run(args, env={'PKI_HOSTNAME':self.fqdn}) - 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 __create_ca_user(self): try: pwd.getpwnam(PKI_USER) - root_logger.debug("ca user %s exists" % PKI_USER) + self.log.debug("ca user %s exists", PKI_USER) except KeyError: - root_logger.debug("adding ca user %s" % PKI_USER) + self.log.debug("adding ca user %s", PKI_USER) args = [paths.USERADD, "-c", "CA System User", "-d", paths.VAR_LIB, "-s", paths.NOLOGIN, "-M", "-r", PKI_USER] try: ipautil.run(args) - root_logger.debug("done adding user") + self.log.debug("done adding user") except ipautil.CalledProcessError, e: - root_logger.critical("failed to add user %s" % e) + self.log.critical("failed to add user %s", e) def __configure_instance(self): # Only used for Dogtag 9 @@ -701,7 +624,7 @@ class CAInstance(service.Service): args = [paths.PERL, paths.PKISILENT, "ConfigureCA", "-cs_hostname", self.fqdn, "-cs_port", str(self.dogtag_constants.ADMIN_SECURE_PORT), - "-client_certdb_dir", self.ca_agent_db, + "-client_certdb_dir", self.agent_db, "-client_certdb_pwd", self.admin_password, "-preop_pin" , preop_pin, "-domain_name", self.security_domain_name, @@ -746,7 +669,7 @@ class CAInstance(service.Service): else: args.append("-external") args.append("false") - if (self.clone): + if self.clone: """sd = security domain --> all CS systems get registered to a security domain. This is set to the hostname and port of the master CA. @@ -785,7 +708,7 @@ class CAInstance(service.Service): ipautil.run(args, env={'PKI_HOSTNAME':self.fqdn}, nolog=nolog) except ipautil.CalledProcessError, e: - root_logger.critical("failed to configure ca instance %s" % e) + self.log.critical("failed to configure ca instance %s", e) raise RuntimeError('Configuration of CA failed') if self.external == 1: @@ -798,15 +721,7 @@ class CAInstance(service.Service): if ipautil.file_exists(paths.ROOT_TMP_CA_P12): shutil.move(paths.ROOT_TMP_CA_P12, paths.CACERT_P12) - root_logger.debug("completed creating ca instance") - - def __restart_instance(self): - try: - self.restart(self.dogtag_constants.PKI_INSTANCE_NAME) - except Exception: - # TODO: roll back here? - root_logger.debug(traceback.format_exc()) - root_logger.critical("Failed to restart the certificate server. See the installation log for details.") + self.log.debug("completed creating ca instance") def __disable_nonce(self): # Turn off Nonces @@ -830,9 +745,9 @@ class CAInstance(service.Service): os.write(admin_fd, self.admin_password) os.close(admin_fd) - # Look thru the cert chain to get all the certs we need to add + # Look through the cert chain to get all the certs we need to add # trust for - p = subprocess.Popen([paths.CERTUTIL, "-d", self.ca_agent_db, + p = subprocess.Popen([paths.CERTUTIL, "-d", self.agent_db, "-O", "-n", "ipa-ca-agent"], stdout=subprocess.PIPE) chain = p.stdout.read() @@ -851,7 +766,7 @@ class CAInstance(service.Service): self.__run_certutil( ['-M', '-t', 'CT,C,C', '-n', nick], - database=self.ca_agent_db, pwd_file=self.admin_password) + database=self.agent_db, pwd_file=self.admin_password) finally: os.remove(admin_name) @@ -867,12 +782,13 @@ class CAInstance(service.Service): '-v', '-n', 'ipa-ca-agent', '-p', self.admin_password, - '-d', self.ca_agent_db, + '-d', self.agent_db, '-r', '/ca/agent/ca/profileReview?requestId=%s' % self.requestId, '%s' % ipautil.format_netloc( self.fqdn, self.dogtag_constants.AGENT_SECURE_PORT), ] - (stdout, stderr, returncode) = ipautil.run(args, nolog=(self.admin_password,)) + (stdout, _stderr, _returncode) = ipautil.run( + args, nolog=(self.admin_password,)) data = stdout.split(self.dogtag_constants.RACERT_LINE_SEP) params = get_defList(data) @@ -888,13 +804,14 @@ class CAInstance(service.Service): '-v', '-n', 'ipa-ca-agent', '-p', self.admin_password, - '-d', self.ca_agent_db, + '-d', self.agent_db, '-e', params, '-r', '/ca/agent/ca/profileProcess', '%s' % ipautil.format_netloc( self.fqdn, self.dogtag_constants.AGENT_SECURE_PORT), ] - (stdout, stderr, returncode) = ipautil.run(args, nolog=(self.admin_password,)) + (stdout, _stderr, _returncode) = ipautil.run( + args, nolog=(self.admin_password,)) data = stdout.split(self.dogtag_constants.RACERT_LINE_SEP) outputList = get_outputList(data) @@ -973,7 +890,7 @@ class CAInstance(service.Service): conn.unbind() - def __run_certutil(self, args, database=None, pwd_file=None,stdin=None): + def __run_certutil(self, args, database=None, pwd_file=None, stdin=None): if not database: database = self.ra_agent_db if not pwd_file: @@ -999,7 +916,7 @@ class CAInstance(service.Service): os.close(f) os.chmod(self.ra_agent_pwd, stat.S_IRUSR) - (stdout, stderr, returncode) = self.__run_certutil(["-N"]) + self.__run_certutil(["-N"]) def __get_ca_chain(self): try: @@ -1016,8 +933,8 @@ class CAInstance(service.Service): try: ipautil.run([paths.PK12UTIL, "-n", "ipa-ca-agent", - "-o", paths.CA_AGENT_P12, - "-d", self.ca_agent_db, + "-o", paths.DOGTAG_AGENT_P12, + "-d", self.agent_db, "-k", pwd_name, "-w", pwd_name]) finally: @@ -1035,14 +952,15 @@ class CAInstance(service.Service): # makes openssl throw up. data = base64.b64decode(chain) - (certlist, stderr, returncode) = ipautil.run([paths.OPENSSL, + (certlist, _stderr, _returncode) = ipautil.run( + [paths.OPENSSL, "pkcs7", "-inform", "DER", "-print_certs", ], stdin=data) - # Ok, now we have all the certificates in certs, walk thru it + # Ok, now we have all the certificates in certs, walk through it # and pull out each certificate and add it to our database st = 1 @@ -1057,7 +975,7 @@ class CAInstance(service.Service): (chain_fd, chain_name) = tempfile.mkstemp() os.write(chain_fd, certlist[st:en+25]) os.close(chain_fd) - (rdn, subject_dn) = certs.get_cert_nickname(certlist[st:en+25]) + (_rdn, subject_dn) = certs.get_cert_nickname(certlist[st:en+25]) if subject_dn == ca_dn: nick = get_ca_nickname(self.realm) trust_flags = 'CT,C,C' @@ -1070,7 +988,7 @@ class CAInstance(service.Service): ) finally: os.remove(chain_name) - subid = subid + 1 + subid += 1 def __request_ra_certificate(self): # Create a noise file for generating our private key @@ -1081,7 +999,10 @@ class CAInstance(service.Service): # Generate our CSR. The result gets put into stdout try: - (stdout, stderr, returncode) = self.__run_certutil(["-R", "-k", "rsa", "-g", "2048", "-s", str(DN(('CN', 'IPA RA'), self.subject_base)), "-z", noise_name, "-a"]) + (stdout, _stderr, _returncode) = self.__run_certutil( + ["-R", "-k", "rsa", "-g", "2048", "-s", + str(DN(('CN', 'IPA RA'), self.subject_base)), + "-z", noise_name, "-a"]) finally: os.remove(noise_name) @@ -1298,75 +1219,32 @@ class CAInstance(service.Service): 'OU=pki-ipa, O=IPA', str(self.subject_base)): print "Updating subject_base in CA template failed" - def enable_client_auth_to_db(self): - """ - Enable client auth connection to the internal db. - """ - caconfig = dogtag.install_constants.CS_CFG_PATH - - with stopped_service(self.dogtag_constants.SERVICE_NAME, - instance_name=self.dogtag_constants.PKI_INSTANCE_NAME): - - # Enable file publishing, disable LDAP - installutils.set_directive(caconfig, - 'authz.instance.DirAclAuthz.ldap.ldapauth.authtype', - 'SslClientAuth', quotes=False, separator='=') - installutils.set_directive(caconfig, - 'authz.instance.DirAclAuthz.ldap.ldapauth.bindDN', - 'uid=pkidbuser,ou=people,o=ipa-ca', quotes=False, separator='=') - installutils.set_directive(caconfig, - 'authz.instance.DirAclAuthz.ldap.ldapauth.clientCertNickname', - 'subsystemCert cert-pki-ca', quotes=False, separator='=') - installutils.set_directive(caconfig, - 'authz.instance.DirAclAuthz.ldap.ldapconn.port', - str(dogtag.install_constants.DS_SECURE_PORT), - quotes=False, separator='=') - installutils.set_directive(caconfig, - 'authz.instance.DirAclAuthz.ldap.ldapconn.secureConn', - 'true', quotes=False, separator='=') - - installutils.set_directive(caconfig, 'internaldb.ldapauth.authtype', - 'SslClientAuth', quotes=False, separator='=') - installutils.set_directive(caconfig, 'internaldb.ldapauth.bindDN', - 'uid=pkidbuser,ou=people,o=ipa-ca', quotes=False, separator='=') - installutils.set_directive(caconfig, - 'internaldb.ldapauth.clientCertNickname', - 'subsystemCert cert-pki-ca', quotes=False, separator='=') - installutils.set_directive(caconfig, 'internaldb.ldapconn.port', - str(dogtag.install_constants.DS_SECURE_PORT), - quotes=False, separator='=') - installutils.set_directive(caconfig, - 'internaldb.ldapconn.secureConn', 'true', quotes=False, - separator='=') - def uninstall(self): - if self.is_configured(): - self.print_msg("Unconfiguring CA") - enabled = self.restore_state("enabled") if not enabled is None and not enabled: self.disable() - # Just eat this state if it exists - installed = self.restore_state("installed") - try: - if self.dogtag_constants.DOGTAG_VERSION >= 10: - ipautil.run([paths.PKIDESTROY, "-i", - self.dogtag_constants.PKI_INSTANCE_NAME, - "-s", "CA"]) - else: + if self.dogtag_constants.DOGTAG_VERSION >= 10: + DogtagInstance.uninstall(self) + else: + if self.is_configured(): + self.print_msg("Unconfiguring CA") + + try: ipautil.run([paths.PKIREMOVE, - "-pki_instance_root=/var/lib", + "-pki_instance_root=%s" % paths.VAR_LIB, "-pki_instance_name=%s" % self.dogtag_constants.PKI_INSTANCE_NAME, "--force"]) - except ipautil.CalledProcessError, e: - root_logger.critical("failed to uninstall CA instance %s" % e) + except ipautil.CalledProcessError, e: + self.log.critical("failed to uninstall CA instance %s", e) + + self.restore_state("installed") # At one time we removed this user on uninstall. That can potentially - # orphan files, or worse, if another useradd runs in the intermim, + # orphan files, or worse, if another useradd runs in the interim, # cause files to have a new owner. - user_exists = self.restore_state("user_exists") + self.restore_state("user_exists") services.knownservices.messagebus.start() cmonger = services.knownservices.certmonger @@ -1383,43 +1261,33 @@ class CAInstance(service.Service): cmonger.stop() # remove CRL files - root_logger.info("Remove old CRL files") + self.log.info("Remove old CRL files") try: for f in get_crl_files(): - root_logger.debug("Remove %s", f) + self.log.debug("Remove %s", f) installutils.remove_file(f) except OSError, e: - root_logger.warning("Error while removing old CRL files: %s" % e) + self.log.warning("Error while removing old CRL files: %s", e) # remove CRL directory - root_logger.info("Remove CRL directory") + self.log.info("Remove CRL directory") if os.path.exists(self.dogtag_constants.CRL_PUBLISH_PATH): try: shutil.rmtree(self.dogtag_constants.CRL_PUBLISH_PATH) except OSError, e: - root_logger.warning("Error while removing CRL publish " - "directory: %s" % e) + self.log.warning("Error while removing CRL publish " + "directory: %s", e) def publish_ca_cert(self, location): args = ["-L", "-n", self.canickname, "-a"] - (cert, err, returncode) = self.__run_certutil(args) + (cert, _err, _returncode) = self.__run_certutil(args) fd = open(location, "w+") fd.write(cert) fd.close() os.chmod(location, 0444) - def __http_proxy(self): - 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(HTTPD_CONFD + "ipa-pki-proxy.conf", "w") as fd: - fd.write(template) - - def configure_certmonger_renewal(self): + @staticmethod + def configure_certmonger_renewal(): """ Create a new CA type for certmonger that will retrieve updated certificates from the dogtag master server. @@ -1450,41 +1318,30 @@ class CAInstance(service.Service): pre_command=None, post_command='renew_ra_cert') except (ipautil.CalledProcessError, RuntimeError), e: - root_logger.error( - "certmonger failed to start tracking certificate: %s" % e) + self.log.error( + "certmonger failed to start tracking certificate: %s", e) def __get_ca_pin(self): try: - return certmonger.get_pin('internal', + return certmonger.get_pin( + 'internal', dogtag_constants=self.dogtag_constants) except IOError, e: raise RuntimeError( 'Unable to determine PIN for CA instance: %s' % e) - def configure_renewal(self): + def configure_cert_renewal(self): + """ + Configure system certificates for renewal. + """ reqs = ( ('auditSigningCert cert-pki-ca', None), ('ocspSigningCert cert-pki-ca', None), ('subsystemCert cert-pki-ca', None), ('caSigningCert cert-pki-ca', 'ipaCACertRenewal'), ) - pin = self.__get_ca_pin() - # Server-Cert cert-pki-ca is renewed per-server - 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: - root_logger.error( - "certmonger failed to start tracking certificate: %s" % e) + DogtagInstance.configure_renewal(self, reqs) def track_servercert(self): """ @@ -1503,8 +1360,22 @@ class CAInstance(service.Service): pre_command=None, post_command=None) except (ipautil.CalledProcessError, RuntimeError), e: + self.log.error( + "certmonger failed to start tracking certificate: %s", e) + + @staticmethod + def stop_tracking_agent_certificate(dogtag_constants): + """Stop tracking agent certificate. Called on uninstall. + """ + cmonger = services.knownservices.certmonger + services.knownservices.messagebus.start() + cmonger.start() + try: + certmonger.stop_tracking(paths.HTTPD_ALIAS_DIR, nickname='ipaCert') + except (ipautil.CalledProcessError, RuntimeError), e: root_logger.error( - "certmonger failed to start tracking certificate: %s" % e) + "certmonger failed to stop tracking certificate: %s", e) + cmonger.stop() def enable_subject_key_identifier(self): """ @@ -1517,7 +1388,7 @@ class CAInstance(service.Service): # this is the default setting from pki-ca/pki-tomcat. Don't touch it # if a user has manually modified it. if setlist == '1,2,3,4,5,6,7,8' or setlist == '1,2,3,4,5,6,7,8,9': - setlist = setlist + ',10' + setlist += ',10' installutils.set_directive( self.dogtag_constants.IPA_SERVICE_PROFILE, 'policyset.serverCertSet.list', @@ -1565,7 +1436,7 @@ class CAInstance(service.Service): # this is the default setting from pki-ca/pki-tomcat. Don't touch it # if a user has manually modified it. if setlist == '1,2,3,4,5,6,7,8,10' or setlist == '1,2,3,4,5,6,7,8,9,10': - setlist = setlist + ',11' + setlist += ',11' installutils.set_directive( self.dogtag_constants.IPA_SERVICE_PROFILE, 'policyset.serverCertSet.list', @@ -1608,13 +1479,14 @@ class CAInstance(service.Service): """ # Check the default validity period of the audit signing cert # and set it to 2 years if it is 6 months. - range = installutils.get_directive( + cert_range = installutils.get_directive( '%s/caSignedLogCert.cfg' % self.dogtag_constants.SERVICE_PROFILE_DIR, 'policyset.caLogSigningSet.2.default.params.range', separator='=' ) - root_logger.debug('caSignedLogCert.cfg profile validity range is %s' % range) - if range == "180": + self.log.debug( + 'caSignedLogCert.cfg profile validity range is %s', cert_range) + if cert_range == "180": installutils.set_directive( '%s/caSignedLogCert.cfg' % self.dogtag_constants.SERVICE_PROFILE_DIR, 'policyset.caLogSigningSet.2.default.params.range', @@ -1629,7 +1501,8 @@ class CAInstance(service.Service): quotes=False, separator='=' ) - root_logger.debug('updated caSignedLogCert.cfg profile validity range to 720') + self.log.debug( + 'updated caSignedLogCert.cfg profile validity range to 720') return True return False @@ -1642,9 +1515,9 @@ class CAInstance(service.Service): dn = DN(('cn', 'CA'), ('cn', fqdn), ('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn) - filter = '(ipaConfigString=caRenewalMaster)' + renewal_filter = '(ipaConfigString=caRenewalMaster)' try: - self.admin_conn.get_entries(base_dn=dn, filter=filter, + self.admin_conn.get_entries(base_dn=dn, filter=renewal_filter, attrs_list=[]) except errors.NotFound: return False @@ -1684,6 +1557,31 @@ class CAInstance(service.Service): self.admin_conn.update_entry(master_entry) + @staticmethod + def update_cert_config(nickname, cert, dogtag_constants=None): + """ + When renewing a CA 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-ca': 'ca.audit_signing.cert', + 'ocspSigningCert cert-pki-ca': 'ca.ocsp_signing.cert', + 'caSigningCert cert-pki-ca': 'ca.signing.cert', + 'subsystemCert cert-pki-ca': 'ca.subsystem.cert', + 'Server-Cert cert-pki-ca': 'ca.sslserver.cert'} + + DogtagInstance.update_cert_cs_cfg( + nickname, cert, directives, + dogtag.configured_constants().CS_CFG_PATH, + dogtag_constants) + def replica_ca_install_check(config): if not config.setup_ca: return @@ -1769,11 +1667,6 @@ def install_replica_ca(config, postinstall=False): if ca.is_installed(): sys.exit("A CA is already configured on this system.") - pkcs12_info = None - if ipautil.file_exists(config.dir + "/dogtagcert.p12"): - pkcs12_info = (config.dir + "/dogtagcert.p12", - config.dir + "/dirsrv_pin.txt") - ca = CAInstance(config.realm_name, certs.NSS_DIR, dogtag_constants=dogtag.install_constants) if postinstall: @@ -1812,32 +1705,6 @@ def install_replica_ca(config, postinstall=False): return ca -def update_cert_config(nickname, cert, dogtag_constants=None): - """ - When renewing a CA 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-ca': 'ca.audit_signing.cert', - 'ocspSigningCert cert-pki-ca': 'ca.ocsp_signing.cert', - 'caSigningCert cert-pki-ca': 'ca.signing.cert', - 'subsystemCert cert-pki-ca': 'ca.subsystem.cert', - 'Server-Cert cert-pki-ca': 'ca.sslserver.cert'} - - with stopped_service(dogtag_constants.SERVICE_NAME, - instance_name=dogtag_constants.PKI_INSTANCE_NAME): - - installutils.set_directive(dogtag.configured_constants().CS_CFG_PATH, - directives[nickname], - base64.b64encode(cert), - quotes=False, separator='=') def update_people_entry(dercert): """ @@ -1874,11 +1741,11 @@ def update_people_entry(dercert): conn.connect( bind_dn=DN(('cn', 'directory manager')), bind_pw=dm_password) - filter = conn.make_filter( + db_filter = conn.make_filter( {'description': ';%s;%s' % (issuer, subject)}, exact=False, trailing_wildcard=False) try: - entries = conn.get_entries(base_dn, conn.SCOPE_SUBTREE, filter) + entries = conn.get_entries(base_dn, conn.SCOPE_SUBTREE, db_filter) except errors.NotFound: entries = [] diff --git a/ipaserver/install/dogtaginstance.py b/ipaserver/install/dogtaginstance.py new file mode 100644 index 000000000..c872f3103 --- /dev/null +++ b/ipaserver/install/dogtaginstance.py @@ -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) diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py index 242e04d99..1719df46d 100644 --- a/ipaserver/install/dsinstance.py +++ b/ipaserver/install/dsinstance.py @@ -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 diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py index 6ad7106b5..dc98d7a51 100644 --- a/ipaserver/install/installutils.py +++ b/ipaserver/install/installutils.py @@ -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 diff --git a/ipaserver/install/ipa_backup.py b/ipaserver/install/ipa_backup.py index 8f27e8a60..0830eb0c5 100644 --- a/ipaserver/install/ipa_backup.py +++ b/ipaserver/install/ipa_backup.py @@ -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, diff --git a/ipaserver/install/ipa_kra_install.py b/ipaserver/install/ipa_kra_install.py new file mode 100644 index 000000000..2c4f2dcaa --- /dev/null +++ b/ipaserver/install/ipa_kra_install.py @@ -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 + diff --git a/ipaserver/install/ipa_replica_prepare.py b/ipaserver/install/ipa_replica_prepare.py index 81b54211f..1099046dd 100644 --- a/ipaserver/install/ipa_replica_prepare.py +++ b/ipaserver/install/ipa_replica_prepare.py @@ -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") diff --git a/ipaserver/install/krainstance.py b/ipaserver/install/krainstance.py new file mode 100644 index 000000000..182e8e034 --- /dev/null +++ b/ipaserver/install/krainstance.py @@ -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 |