From aaebc76f590a31f6dc44efa98dba950985dc6dd2 Mon Sep 17 00:00:00 2001 From: Matthew Harmsen Date: Thu, 16 Aug 2012 20:09:20 -0700 Subject: PKI Deployment Scriptlets * TRAC Ticket #266 - for non-master CA subsystems, pkidestroy needs to contact the security domain to update the domain * Made Fedora 17 rely upon tomcatjss 7.0.0 or later * Changed Dogtag 10 build-time and runtime requirements for 'pki-deploy' * Altered PKI Package Dependency Chain (top-to-bottom): pki-ca, pki-kra, pki-ocsp, pki-tks --> pki-deploy --> pki-common * Changed TPS to require a build-time dependency of 'httpd-devel >= 2.4.2' * Clarified RPM build script's usage message --- base/deploy/src/scriptlets/configuration.py | 1 + base/deploy/src/scriptlets/pkihelper.py | 190 ++++++++++++++++++++++++++++ base/deploy/src/scriptlets/pkimessages.py | 25 ++++ base/deploy/src/scriptlets/pkiparser.py | 26 ++++ 4 files changed, 242 insertions(+) (limited to 'base/deploy') diff --git a/base/deploy/src/scriptlets/configuration.py b/base/deploy/src/scriptlets/configuration.py index f7a9a66e6..7e99dd4fe 100644 --- a/base/deploy/src/scriptlets/configuration.py +++ b/base/deploy/src/scriptlets/configuration.py @@ -147,6 +147,7 @@ class PkiScriptlet(pkiscriptlet.AbstractBasePkiScriptlet): def destroy(self): config.pki_log.info(log.CONFIGURATION_DESTROY_1, __name__, extra=config.PKI_INDENTATION_LEVEL_1) + util.security_domain.deregister() if not config.pki_dry_run_flag: if master['pki_subsystem'] in config.PKI_APACHE_SUBSYSTEMS and\ util.instance.apache_instance_subsystems() == 1: diff --git a/base/deploy/src/scriptlets/pkihelper.py b/base/deploy/src/scriptlets/pkihelper.py index c0dc14d24..038198ad3 100644 --- a/base/deploy/src/scriptlets/pkihelper.py +++ b/base/deploy/src/scriptlets/pkihelper.py @@ -27,6 +27,7 @@ import os import fileinput import pickle import random +import re import shutil import string import subprocess @@ -46,6 +47,7 @@ from pkiconfig import pki_slots_dict as slots from pkiconfig import pki_selinux_config_ports as ports import pkimanifest as manifest import pkimessages as log +from pkiparser import read_simple_configuration_file # PKI Deployment Helper Functions @@ -2400,6 +2402,193 @@ class certutil: return +# PKI Deployment Security Domain Class +class security_domain: + def deregister(self, critical_failure=False): + try: + # process this PKI subsystem instance's 'CS.cfg' + cs_cfg = read_simple_configuration_file(master['pki_target_cs_cfg']) + + # assign key name/value pairs + machinename = cs_cfg.get('service.machineName') + sport = cs_cfg.get('service.securityDomainPort') + ncsport = cs_cfg.get('service.non_clientauth_securePort', '') + sechost = cs_cfg.get('securitydomain.host') + httpport = cs_cfg.get('securitydomain.httpport') + seceeport = cs_cfg.get('securitydomain.httpseeport') + secagentport = cs_cfg.get('securitydomain.httpsagentport') + secadminport = cs_cfg.get('securitydomain.httpsadminport') + secname = cs_cfg.get('securitydomain.name', 'unknown') + secselect = cs_cfg.get('securitydomain.select') + adminsport = cs_cfg.get('pkicreate.admin_secure_port', '') + typeval = cs_cfg.get('cs.type', '') + agentsport = cs_cfg.get('pkicreate.agent_secure_port', '') + token_pwd = None + + # retrieve subsystem nickname + subsystemnick_param = typeval.lower() + ".cert.subsystem.nickname" + subsystemnick = cs_cfg.get(subsystemnick_param) + if subsystemnick is None: + config.pki_log.warning( + log.PKIHELPER_SECURITY_DOMAIN_UPDATE_FAILURE_2, + typeval, + secname, + extra=config.PKI_INDENTATION_LEVEL_2) + config.pki_log.error( + log.PKIHELPER_UNDEFINED_SUBSYSTEM_NICKNAME, + extra=config.PKI_INDENTATION_LEVEL_2) + if critical_failure == True: + sys.exit(-1) + else: + return + + # retrieve name of token based upon type (hardware/software) + if ':' in subsystemnick: + token_name = subsystemnick.split(':')[0] + else: + token_name = "internal" + + # NOTE: Don't check for the existence of 'httpport', as this will + # be undefined for a Security Domain that has been migrated! + if sechost is None or\ + seceeport is None or\ + secagentport is None or\ + secadminport is None: + config.pki_log.warning( + log.PKIHELPER_SECURITY_DOMAIN_UPDATE_FAILURE_2, + typeval, + secname, + extra=config.PKI_INDENTATION_LEVEL_2) + config.pki_log.error( + log.PKIHELPER_SECURITY_DOMAIN_UNDEFINED, + extra=config.PKI_INDENTATION_LEVEL_2) + if critical_failure == True: + sys.exit(-1) + else: + return + + if secselect != "new": + # This is not a domain master, so we need to update the master + config.pki_log.info(log.PKIHELPER_SECURITY_DOMAIN_CONTACT_1, + secname, + extra=config.PKI_INDENTATION_LEVEL_2) + listval = typeval.lower() + "List" + urlheader = "https://{}:{}".format(sechost, seceeport) + urlagentheader = "https://{}:{}".format(sechost, secagentport) + urladminheader = "https://{}:{}".format(sechost, secadminport) + updateURL = "/ca/agent/ca/updateDomainXML" + + # process this PKI subsystem instance's 'password.conf' + # + # REMINDER: NEVER log this 'sensitive' information! + # + if os.path.exists(master['pki_shared_password_conf']) and\ + os.path.isfile(master['pki_shared_password_conf']) and\ + os.access(master['pki_shared_password_conf'], os.R_OK): + tokens = read_simple_configuration_file( + master['pki_shared_password_conf']) + hardware_token = "hardware-" + token_name + if tokens.has_key(hardware_token): + token_name = hardware_token + token_pwd = tokens[hardware_token] + elif tokens.has_key(token_name): + token_pwd = tokens[token_name] + + if token_pwd is None or token_pwd == '': + # 'pkiremove' prompts with + # "What is the password for this token?" + config.pki_log.warning( + log.PKIHELPER_SECURITY_DOMAIN_UPDATE_FAILURE_2, + typeval, + secname, + extra=config.PKI_INDENTATION_LEVEL_2) + config.pki_log.error(log.PKIHELPER_PASSWORD_NOT_FOUND_1, + token_name, + extra=config.PKI_INDENTATION_LEVEL_2) + if critical_failure == True: + sys.exit(-1) + else: + return + + params = "name=" + "\"" + master['pki_instance_path'] + "\"" +\ + "&type=" + str(typeval) +\ + "&list=" + str(listval) +\ + "&host=" + str(machinename) +\ + "&sport=" + str(sport) +\ + "&ncsport=" + str(ncsport) +\ + "&adminsport=" + str(adminsport) +\ + "&agentsport=" + str(agentsport) +\ + "&operation=remove" + + # Compose this "sslget" command + # + # REMINDER: NEVER log this command as it contains + # an exposed password in plaintext! + # + command = "/usr/bin/sslget -n '{}' -p '{}' -d '{}' -e '{}' "\ + "-v -r '{}' {}:{} 2>&1".format( + subsystemnick, token_pwd, + master['pki_database_path'], + params, updateURL, + sechost, secagentport) + # update domainXML + if not config.pki_dry_run_flag: + # Execute this "sslget" command + output = subprocess.check_output(command, + stderr=subprocess.STDOUT, + shell=True) + config.pki_log.debug(log.PKIHELPER_SSLGET_OUTPUT_1, + output, + extra=config.PKI_INDENTATION_LEVEL_2) + # Search the output for Status + status = re.findall("\(.*?)\<\/Status\>", output) + if not status: + config.pki_log.warning( + log.PKIHELPER_SECURITY_DOMAIN_UNREACHABLE_1, + secname, + extra=config.PKI_INDENTATION_LEVEL_2) + if critical_failure == True: + sys.exit(-1) + elif status[0] != "0": + error = re.findall("\(.*?)\<\/Error\>", output) + if not error: + error = "" + config.pki_log.warning( + log.PKIHELPER_SECURITY_DOMAIN_UNREGISTERED_2, + typeval, + secname, + extra=config.PKI_INDENTATION_LEVEL_2) + config.pki_log.error( + log.PKIHELPER_SECURITY_DOMAIN_UPDATE_FAILURE_3, + typeval, + secname, + error[0], + extra=config.PKI_INDENTATION_LEVEL_2) + if critical_failure == True: + sys.exit(-1) + else: + config.pki_log.info( + log.PKIHELPER_SECURITY_DOMAIN_UPDATE_SUCCESS_2, + typeval, + secname, + extra=config.PKI_INDENTATION_LEVEL_2) + except subprocess.CalledProcessError as exc: + config.pki_log.warning( + log.PKIHELPER_SECURITY_DOMAIN_UPDATE_FAILURE_2, + typeval, + secname, + extra=config.PKI_INDENTATION_LEVEL_2) + config.pki_log.warning( + log.PKIHELPER_SECURITY_DOMAIN_UNREACHABLE_1, + secname, + extra=config.PKI_INDENTATION_LEVEL_2) + config.pki_log.error(log.PKI_SUBPROCESS_ERROR_1, exc, + extra=config.PKI_INDENTATION_LEVEL_2) + if critical_failure == True: + sys.exit(-1) + return + + # PKI Deployment 'systemd' Execution Management Class class systemd: def start(self, critical_failure=True): @@ -2535,5 +2724,6 @@ symlink = symlink() war = war() password = password() certutil = certutil() +security_domain = security_domain() systemd = systemd() jython = jython() diff --git a/base/deploy/src/scriptlets/pkimessages.py b/base/deploy/src/scriptlets/pkimessages.py index 2b8a22528..ba3f22898 100644 --- a/base/deploy/src/scriptlets/pkimessages.py +++ b/base/deploy/src/scriptlets/pkimessages.py @@ -216,14 +216,38 @@ PKIHELPER_NAMESPACE_RESERVED_NAME_2 = "PKI instance '%s' is already a "\ PKIHELPER_NOISE_FILE_2 = "generating noise file called '%s' and "\ "filling it with '%d' random bytes" PKIHELPER_PASSWORD_CONF_1 = "generating '%s'" +PKIHELPER_PASSWORD_NOT_FOUND_1 = "no password found for '%s'!" PKIHELPER_PKI_INSTANCE_SUBSYSTEMS_2 = "instance '%s' contains '%d' "\ "PKI subsystems" PKIHELPER_REMOVE_FILTER_SECTION_1 = "removing filter section from '%s'" PKIHELPER_RM_F_1 = "rm -f %s" PKIHELPER_RM_RF_1 = "rm -rf %s" PKIHELPER_RMDIR_1 = "rmdir %s" +PKIHELPER_SECURITY_DOMAIN_CONTACT_1 =\ + "contacting the security domain master to update security domain '%s'" +PKIHELPER_SECURITY_DOMAIN_UNDEFINED =\ + "No security domain defined.\n"\ + "If this is an unconfigured instance, then that is OK.\n"\ + "Otherwise, manually delete the entry from the security domain master." +PKIHELPER_SECURITY_DOMAIN_UNREACHABLE_1 =\ + "security domain '%s' may be offline or unreachable!" +PKIHELPER_SECURITY_DOMAIN_UNREGISTERED_2 =\ + "this '%s' entry may not be registered with security domain '%s'!" +PKIHELPER_SECURITY_DOMAIN_UPDATE_FAILURE_2 =\ + "this '%s' entry will NOT be deleted from security domain '%s'!" +PKIHELPER_SECURITY_DOMAIN_UPDATE_FAILURE_3 =\ + "updateDomainXML FAILED to delete this '%s' entry from "\ + "security domain '%s': '%s'" +PKIHELPER_SECURITY_DOMAIN_UPDATE_SUCCESS_2 =\ + "updateDomainXML SUCCESSFULLY deleted this '%s' entry from "\ + "security domain '%s'" PKIHELPER_SET_MODE_1 = "setting ownerships, permissions, and acls on '%s'" PKIHELPER_SLOT_SUBSTITUTION_2 = "slot substitution: '%s' ==> '%s'" +PKIHELPER_SSLGET_OUTPUT_1 = "\n"\ + "Dump of 'sslget' output:\n"\ + "=====================================================\n"\ + "%s\n"\ + "=====================================================" PKIHELPER_SYSTEMD_COMMAND_1 = "executing '%s'" PKIHELPER_TOMCAT_INSTANCE_SUBSYSTEMS_2 = "instance '%s' contains '%d' "\ "Tomcat PKI subsystems" @@ -236,6 +260,7 @@ PKIHELPER_UNDEFINED_CLIENT_DATABASE_PASSWORD_2 =\ "the randomly generated client pin MUST be used" PKIHELPER_UNDEFINED_CONFIGURATION_FILE_ENTRY_2 =\ "A value for '%s' MUST be defined in '%s'" +PKIHELPER_UNDEFINED_SUBSYSTEM_NICKNAME = "subsystem nickname not defined" PKIHELPER_USER_1 = "retrieving UID for '%s' . . ." PKIHELPER_USER_ADD_2 = "adding UID '%s' for user '%s' . . ." PKIHELPER_USER_ADD_DEFAULT_2 = "adding default UID '%s' for user '%s' . . ." diff --git a/base/deploy/src/scriptlets/pkiparser.py b/base/deploy/src/scriptlets/pkiparser.py index dd1f93bd3..1fe74e835 100644 --- a/base/deploy/src/scriptlets/pkiparser.py +++ b/base/deploy/src/scriptlets/pkiparser.py @@ -188,6 +188,32 @@ def process_command_line_arguments(argv): return +# The following code is based heavily upon +# "http://www.decalage.info/en/python/configparser" +COMMENT_CHAR = '#' +OPTION_CHAR = '=' + +def read_simple_configuration_file(filename): + values = {} + f = open(filename) + for line in f: + # First, remove comments: + if COMMENT_CHAR in line: + # split on comment char, keep only the part before + line, comment = line.split(COMMENT_CHAR, 1) + # Second, find lines with an name=value: + if OPTION_CHAR in line: + # split on name char: + name, value = line.split(OPTION_CHAR, 1) + # strip spaces: + name = name.strip() + value = value.strip() + # store in dictionary: + values[name] = value + f.close() + return values + + def read_pki_configuration_file(): "Read configuration file sections into dictionaries" rv = 0 -- cgit