URL: https://github.com/freeipa/freeipa/pull/367 Author: stlaz Title: #367: Remove nsslib from IPA Action: synchronized
To pull the PR as Git branch: git remote add ghfreeipa https://github.com/freeipa/freeipa git fetch ghfreeipa pull/367/head:pr367 git checkout pr367
From 6038830d9489cdfde4e7ac700a93c0fb2e99c7aa Mon Sep 17 00:00:00 2001 From: Stanislav Laznicka <slazn...@redhat.com> Date: Tue, 20 Dec 2016 10:05:36 +0100 Subject: [PATCH 1/7] Remove NSSConnection from the Python RPC module NSSConnection was causing a lot of trouble in the past and there is a lot of logic around it just to make it not fail. What's more, when using NSS to create an SSL connection in FIPS mode, NSS always requires database password which makes the `ipa` command totally unusable. NSSConnection is therefore replaced with Python's httplib.HTTPSConnection which is OpenSSL based. https://fedorahosted.org/freeipa/ticket/5695 --- ipalib/config.py | 3 ++ ipalib/constants.py | 1 + ipalib/rpc.py | 69 ++++++++------------------------------ ipalib/util.py | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 113 insertions(+), 55 deletions(-) diff --git a/ipalib/config.py b/ipalib/config.py index 20591db..8ecada6 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -493,6 +493,9 @@ def _bootstrap(self, **overrides): if 'nss_dir' not in self: self.nss_dir = self._join('confdir', 'nssdb') + if 'ca_certfile' not in self: + self.ca_certfile = self._join('confdir', 'ca.crt') + # Set plugins_on_demand: if 'plugins_on_demand' not in self: self.plugins_on_demand = (self.context == 'cli') diff --git a/ipalib/constants.py b/ipalib/constants.py index 81643da..4f40545 100644 --- a/ipalib/constants.py +++ b/ipalib/constants.py @@ -226,6 +226,7 @@ ('conf_default', object), # File containing context independent config ('plugins_on_demand', object), # Whether to finalize plugins on-demand (bool) ('nss_dir', object), # Path to nssdb, default {confdir}/nssdb + ('ca_certfile', object), # Path to CA cert file # Set in Env._finalize_core(): ('in_server', object), # Whether or not running in-server (bool) diff --git a/ipalib/rpc.py b/ipalib/rpc.py index 921f5cb..66cd1c3 100644 --- a/ipalib/rpc.py +++ b/ipalib/rpc.py @@ -44,7 +44,7 @@ import gssapi from dns import resolver, rdatatype from dns.exception import DNSException -from nss.error import NSPRError +from ssl import SSLError import six from six.moves import urllib @@ -60,8 +60,7 @@ from ipapython.cookie import Cookie from ipapython.dnsutil import DNSName from ipalib.text import _ -import ipapython.nsslib -from ipapython.nsslib import NSSConnection +from ipalib.util import IPAHTTPSConnection from ipalib.krb_utils import KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN, KRB5KRB_AP_ERR_TKT_EXPIRED, \ KRB5_FCC_PERM, KRB5_FCC_NOFILE, KRB5_CC_FORMAT, \ KRB5_REALM_CANT_RESOLVE, KRB5_CC_NOTFOUND, get_principal @@ -470,48 +469,21 @@ def get_host_info(self, host): return (host, extra_headers, x509) + class SSLTransport(LanguageAwareTransport): """Handles an HTTPS transaction to an XML-RPC server.""" - - def get_connection_dbdir(self): - """ - If there is a connections open it may have already initialized - NSS database. Return the database location used by the connection. - """ - for value in context.__dict__.values(): - if not isinstance(value, Connection): - continue - if not isinstance( - getattr(value.conn, '_ServerProxy__transport', None), - SSLTransport): - continue - if hasattr(value.conn._ServerProxy__transport, 'dbdir'): - return value.conn._ServerProxy__transport.dbdir - return None - def make_connection(self, host): host, self._extra_headers, _x509 = self.get_host_info(host) if self._connection and host == self._connection[0]: return self._connection[1] - dbdir = context.nss_dir - connection_dbdir = self.get_connection_dbdir() + ca_certfile = context.ca_certfile - if connection_dbdir: - # If an existing connection is already using the same NSS - # database there is no need to re-initialize. - no_init = dbdir == connection_dbdir - - else: - # If the NSS database is already being used there is no - # need to re-initialize. - no_init = dbdir == ipapython.nsslib.current_dbdir - - conn = NSSConnection(host, 443, dbdir=dbdir, no_init=no_init, - tls_version_min=api.env.tls_version_min, - tls_version_max=api.env.tls_version_max) - self.dbdir=dbdir + conn = IPAHTTPSConnection( + host, 443, cafile=ca_certfile, + tls_version_min=api.env.tls_version_min, + tls_version_max=api.env.tls_version_max) conn.connect() @@ -883,15 +855,15 @@ def apply_session_cookie(self, url): return session_url def create_connection(self, ccache=None, verbose=None, fallback=None, - delegate=None, nss_dir=None): + delegate=None, ca_certfile=None): if verbose is None: verbose = self.api.env.verbose if fallback is None: fallback = self.api.env.fallback if delegate is None: delegate = self.api.env.delegate - if nss_dir is None: - nss_dir = self.api.env.nss_dir + if ca_certfile is None: + ca_certfile = self.api.env.ca_certfile try: rpc_uri = self.env[self.env_rpc_uri_key] principal = get_principal() @@ -903,7 +875,7 @@ def create_connection(self, ccache=None, verbose=None, fallback=None, except (errors.CCacheError, ValueError): # No session key, do full Kerberos auth pass - context.nss_dir = nss_dir + context.ca_certfile = ca_certfile urls = self.get_url_list(rpc_uri) serverproxy = None for url in urls: @@ -1012,7 +984,7 @@ def forward(self, name, *args, **kw): error=e.faultString, server=server, ) - except NSPRError as e: + except SSLError as e: raise NetworkError(uri=server, error=str(e)) except ProtocolError as e: # By catching a 401 here we can detect the case where we have @@ -1029,22 +1001,9 @@ def forward(self, name, *args, **kw): # This shouldn't happen if we have a session but it isn't fatal. pass - # Create a new serverproxy with the non-session URI. If there - # is an existing connection we need to save the NSS dbdir so - # we can skip an unnecessary NSS_Initialize() and avoid - # NSS_Shutdown issues. + # Create a new serverproxy with the non-session URI serverproxy = self.create_connection(os.environ.get('KRB5CCNAME'), self.env.verbose, self.env.fallback, self.env.delegate) - - dbdir = None - current_conn = getattr(context, self.id, None) - if current_conn is not None: - dbdir = getattr(current_conn.conn._ServerProxy__transport, 'dbdir', None) - if dbdir is not None: - self.debug('Using dbdir %s' % dbdir) setattr(context, self.id, Connection(serverproxy, self.disconnect)) - if dbdir is not None: - current_conn = getattr(context, self.id, None) - current_conn.conn._ServerProxy__transport.dbdir = dbdir return self.forward(name, *args, **kw) raise NetworkError(uri=server, error=e.errmsg) except socket.error as e: diff --git a/ipalib/util.py b/ipalib/util.py index 1c00cd7..1ee7755 100644 --- a/ipalib/util.py +++ b/ipalib/util.py @@ -33,6 +33,7 @@ import dns import encodings import sys +import ssl from weakref import WeakKeyDictionary import netaddr @@ -42,6 +43,12 @@ from netaddr.core import AddrFormatError import six +try: + from httplib import HTTPSConnection +except ImportError: + # Python 3 + from http.client import HTTPSConnection + from ipalib import errors, messages from ipalib.constants import DOMAIN_LEVEL_0 from ipalib.text import _ @@ -51,6 +58,12 @@ from ipapython.dnsutil import resolve_ip_addresses from ipapython.ipa_log_manager import root_logger +try: + from ipaplatform.paths import paths + IPA_CA_CRT = paths.IPA_CA_CRT +except ImportError: + IPA_CA_CRT = '/etc/ipa/ca.crt' + if six.PY3: unicode = str @@ -187,6 +200,88 @@ def normalize_zone(zone): return zone +class IPAHTTPSConnection(HTTPSConnection, object): + """ + This class inherits from `object` because HTTPSConnection does not in + Python 2.7. This is to allow the use of super() in its and derived + classes' methods. + """ + + # pylint: disable=no-member + tls_cutoff_map = { + "ssl2": ssl.OP_NO_SSLv2, + "tls1.0": ssl.OP_NO_TLSv1, + "tls1.1": ssl.OP_NO_TLSv1_1, + "tls1.2": ssl.OP_NO_TLSv1_2, + } + # pylint: enable=no-member + + def __init__(self, host, port=HTTPSConnection.default_port, + cafile=IPA_CA_CRT, + client_certfile=None, client_keyfile=None, + tls_version_min="tls1.1", + tls_version_max="tls1.2", + **kwargs): + """ + Set up a client HTTPS connection. + + :param host: The host to connect to + :param port: The port to connect to, defaults to + HTTPSConnection.default_port + :param cafile: A PEM-format file containning the trusted + CA certificates + :param client_certfile: + A PEM-format client certificate file that will be used to + identificate the user to the server. It should also contain + the client private key. + :returns An established HTTPS connection to host:port + """ + # pylint: disable=no-member + tls_cutoff = [ + ssl.OP_NO_SSLv2, + ssl.OP_NO_TLSv1, + ssl.OP_NO_TLSv1_1, + ssl.OP_NO_TLSv1_2 + ] + # remove the slice of negating protocol options according to options + min_idx = tls_cutoff.index(self.tls_cutoff_map[tls_version_min]) + max_idx = tls_cutoff.index(self.tls_cutoff_map[tls_version_max]) + tls_use = tls_cutoff[min_idx:max_idx+1] + del(tls_cutoff[min_idx:max_idx+1]) + + # official Python documentation states that the best option to get + # TLSv1 and later is to setup SSLContext with PROTOCOL_SSLv23 + # and then negate the insecure SSLv2 and SSLv3 + ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23) + ctx.options |= ( + ssl.OP_ALL | ssl.OP_NO_COMPRESSION | ssl.OP_SINGLE_DH_USE | + ssl.OP_SINGLE_ECDH_USE | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 + ) + + # high ciphers without RC4, MD5, TripleDES, pre-shared key + # and secure remote password + ctx.set_ciphers("HIGH:!aNULL:!eNULL:!MD5:!RC4:!3DES:!PSK:!SRP") + + # pylint: enable=no-member + for version in tls_cutoff: + ctx.options |= version + + # make sure the given TLS version is available if Python decides to + # remove it from default TLS flags + for version in tls_use: + ctx.options &= ~version + + ctx.verify_mode = ssl.CERT_REQUIRED + ctx.check_hostname = True + ctx.load_verify_locations(cafile) + + if client_certfile is not None: + ctx.load_cert_chain(client_certfile) + + super(IPAHTTPSConnection, self).__init__(host, port, context=ctx, + **kwargs) + + def validate_dns_label(dns_label, allow_underscore=False, allow_slash=False): base_chars = 'a-z0-9' extra_chars = '' From e94145fde9f36891f9ef4fab8ba6807d080ae981 Mon Sep 17 00:00:00 2001 From: Stanislav Laznicka <slazn...@redhat.com> Date: Mon, 2 Jan 2017 17:00:00 +0100 Subject: [PATCH 2/7] Move RA agent certificate file export to a different location HTTPS connection to certificate server requires client authentication so we need a file with client certificate and private key prior to its first occurence which happens during migration of certificate profiles to LDAP. https://fedorahosted.org/freeipa/ticket/5695 https://fedorahosted.org/freeipa/ticket/6392 --- install/restart_scripts/renew_ra_cert | 4 ++-- ipaplatform/base/paths.py | 2 +- ipaserver/install/cainstance.py | 5 ++++- ipaserver/install/dogtaginstance.py | 6 +++--- ipaserver/install/ipa_backup.py | 2 +- ipaserver/install/krainstance.py | 7 +------ ipaserver/install/server/upgrade.py | 11 +++++------ ipaserver/plugins/dogtag.py | 2 +- 8 files changed, 18 insertions(+), 21 deletions(-) diff --git a/install/restart_scripts/renew_ra_cert b/install/restart_scripts/renew_ra_cert index d978f94..4dc6c2e 100644 --- a/install/restart_scripts/renew_ra_cert +++ b/install/restart_scripts/renew_ra_cert @@ -29,7 +29,7 @@ import traceback from ipalib.install.kinit import kinit_keytab from ipalib import api -from ipaserver.install import certs, cainstance, krainstance +from ipaserver.install import certs, cainstance, dogtaginstance from ipaplatform.paths import paths @@ -61,7 +61,7 @@ def _main(): cainstance.update_people_entry(dercert) if api.Command.kra_is_enabled()['result']: - krainstance.export_kra_agent_pem() + dogtaginstance.export_ra_agent_pem() finally: shutil.rmtree(tmpdir) api.Backend.ldap2.disconnect() diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py index 896fa9d..d7f679a 100644 --- a/ipaplatform/base/paths.py +++ b/ipaplatform/base/paths.py @@ -135,7 +135,7 @@ class BasePathNamespace(object): ROOT_IPA_CACHE = "/root/.ipa_cache" ROOT_PKI = "/root/.pki" DOGTAG_ADMIN_P12 = "/root/ca-agent.p12" - KRA_AGENT_PEM = "/etc/httpd/alias/kra-agent.pem" + RA_AGENT_PEM = "/var/lib/ipa/ra-agent.pem" CACERT_P12 = "/root/cacert.p12" ROOT_IPA_CSR = "/root/ipa.csr" NAMED_PID = "/run/named/named.pid" diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py index c7e81f0..e43adea 100644 --- a/ipaserver/install/cainstance.py +++ b/ipaserver/install/cainstance.py @@ -65,7 +65,8 @@ from ipaserver.install import ldapupdate from ipaserver.install import replication from ipaserver.install import sysupgrade -from ipaserver.install.dogtaginstance import DogtagInstance +from ipaserver.install.dogtaginstance import ( + DogtagInstance, export_ra_agent_pem) from ipaserver.plugins import ldap2 # We need to reset the template because the CA uses the regular boot @@ -413,6 +414,8 @@ def configure_instance(self, host_name, dm_password, admin_password, else: self.step("importing RA certificate from PKCS #12 file", lambda: self.import_ra_cert(ra_p12)) + self.step("exporting KRA agent cert", export_ra_agent_pem) + if not ra_only: self.step("importing CA chain to RA certificate database", self.__import_ca_chain) self.step("setting up signing cert profile", self.__setup_sign_profile) diff --git a/ipaserver/install/dogtaginstance.py b/ipaserver/install/dogtaginstance.py index f4856c7..e6bbb39 100644 --- a/ipaserver/install/dogtaginstance.py +++ b/ipaserver/install/dogtaginstance.py @@ -74,11 +74,11 @@ def is_installing_replica(sys_type): return False -def export_kra_agent_pem(): +def export_ra_agent_pem(): """ Export ipaCert with private key for client authentication. """ - fd, filename = tempfile.mkstemp(dir=paths.HTTPD_ALIAS_DIR) + fd, filename = tempfile.mkstemp(dir=paths.VAR_LIB_IPA) os.close(fd) args = ["/usr/bin/pki", @@ -92,7 +92,7 @@ def export_kra_agent_pem(): os.chown(filename, 0, pent.pw_gid) os.chmod(filename, 0o440) - os.rename(filename, paths.KRA_AGENT_PEM) + os.rename(filename, paths.RA_AGENT_PEM) class DogtagInstance(service.Service): diff --git a/ipaserver/install/ipa_backup.py b/ipaserver/install/ipa_backup.py index c11120b..cf419ae 100644 --- a/ipaserver/install/ipa_backup.py +++ b/ipaserver/install/ipa_backup.py @@ -155,7 +155,7 @@ class Backup(admintool.AdminTool): paths.SMB_CONF, paths.SAMBA_KEYTAB, paths.DOGTAG_ADMIN_P12, - paths.KRA_AGENT_PEM, + paths.RA_AGENT_PEM, paths.CACERT_P12, paths.KRB5KDC_KDC_CONF, paths.SYSTEMD_IPA_SERVICE, diff --git a/ipaserver/install/krainstance.py b/ipaserver/install/krainstance.py index 554811c..b4f65fd 100644 --- a/ipaserver/install/krainstance.py +++ b/ipaserver/install/krainstance.py @@ -36,8 +36,7 @@ from ipaserver.install import cainstance from ipaserver.install import installutils from ipaserver.install import ldapupdate -from ipaserver.install.dogtaginstance import (export_kra_agent_pem, - DogtagInstance) +from ipaserver.install.dogtaginstance import DogtagInstance from ipaserver.plugins import ldap2 from ipapython.ipa_log_manager import log_mgr @@ -115,7 +114,6 @@ def configure_instance(self, realm_name, host_name, dm_password, if not self.clone: self.step("create KRA agent", self.__create_kra_agent) - self.step("exporting KRA agent cert", export_kra_agent_pem) if not ra_only: if promote: self.step("destroying installation admin user", self.teardown_admin) @@ -272,9 +270,6 @@ def __spawn_instance(self): os.remove(cfg_file) shutil.move(paths.KRA_BACKUP_KEYS_P12, paths.KRACERT_P12) - - export_kra_agent_pem() - self.log.debug("completed creating KRA instance") def __create_kra_agent(self): diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py index 5d8e596..fa6f17f 100644 --- a/ipaserver/install/server/upgrade.py +++ b/ipaserver/install/server/upgrade.py @@ -45,7 +45,6 @@ from ipaserver.install import custodiainstance from ipaserver.install import sysupgrade from ipaserver.install import dnskeysyncinstance -from ipaserver.install import krainstance from ipaserver.install import dogtaginstance from ipaserver.install import krbinstance from ipaserver.install import adtrustinstance @@ -1416,10 +1415,10 @@ def fix_trust_flags(): sysupgrade.set_upgrade_state('http', 'fix_trust_flags', True) -def export_kra_agent_pem(): +def export_ra_agent_pem(): root_logger.info('[Exporting KRA agent PEM file]') - if sysupgrade.get_upgrade_state('http', 'export_kra_agent_pem'): + if sysupgrade.get_upgrade_state('http', 'export_ra_agent_pem'): root_logger.info("KRA agent PEM file already exported") return @@ -1427,9 +1426,9 @@ def export_kra_agent_pem(): root_logger.info("KRA is not enabled") return - krainstance.export_kra_agent_pem() + dogtaginstance.export_ra_agent_pem() - sysupgrade.set_upgrade_state('http', 'export_kra_agent_pem', True) + sysupgrade.set_upgrade_state('http', 'export_ra_agent_pem', True) def update_mod_nss_protocol(http): @@ -1660,7 +1659,7 @@ def upgrade_configuration(): update_mod_nss_protocol(http) update_mod_nss_cipher_suite(http) fix_trust_flags() - export_kra_agent_pem() + export_ra_agent_pem() http.start() uninstall_selfsign(ds, http) diff --git a/ipaserver/plugins/dogtag.py b/ipaserver/plugins/dogtag.py index 73c14ed..40b897d 100644 --- a/ipaserver/plugins/dogtag.py +++ b/ipaserver/plugins/dogtag.py @@ -2025,7 +2025,7 @@ def get_client(self): str(self.kra_port), 'kra') - connection.set_authentication_cert(paths.KRA_AGENT_PEM) + connection.set_authentication_cert(paths.RA_AGENT_PEM) return KRAClient(connection, crypto) From 49140a7121426d8a7214b1b3d0d161c8b0ff3116 Mon Sep 17 00:00:00 2001 From: Stanislav Laznicka <slazn...@redhat.com> Date: Wed, 4 Jan 2017 08:41:26 +0100 Subject: [PATCH 3/7] Don't run kra.configure_instance if not necessary If kra should not be set up, don't run the code as it would only prolong the installations. Previously, krainstance configuration would be performed just to export the client certificate and private key to authenticate to certificate server. This is now performed somewhere else therefore there's no need to run KRAInstance.configure_instance. The kra.install() method still performs actions on replicas and we're keeping it in server installer to conform to the installers design. https://fedorahosted.org/freeipa/ticket/5695 --- ipaserver/install/kra.py | 16 +++++----- ipaserver/install/krainstance.py | 65 ++++++++++++++++++++-------------------- 2 files changed, 40 insertions(+), 41 deletions(-) diff --git a/ipaserver/install/kra.py b/ipaserver/install/kra.py index 0d1ed8e..7cfbae0 100644 --- a/ipaserver/install/kra.py +++ b/ipaserver/install/kra.py @@ -70,6 +70,8 @@ def install_check(api, replica_config, options): def install(api, replica_config, options): if replica_config is None: + if not options.setup_kra: + return realm_name = api.env.realm dm_password = options.dm_password host_name = api.env.host @@ -77,7 +79,6 @@ def install(api, replica_config, options): pkcs12_info = None master_host = None - ra_only = not options.setup_kra promote = False else: krafile = os.path.join(replica_config.dir, 'kracert.p12') @@ -97,6 +98,9 @@ def install(api, replica_config, options): " cacert.p12 file not found in replica file") shutil.copy(cafile, krafile) + if not replica_config.setup_kra: + return + realm_name = replica_config.realm_name dm_password = replica_config.dirman_password host_name = replica_config.host_name @@ -104,7 +108,6 @@ def install(api, replica_config, options): pkcs12_info = (krafile,) master_host = replica_config.kra_host_name - ra_only = not replica_config.setup_kra promote = options.promote kra = krainstance.KRAInstance(realm_name) @@ -112,18 +115,15 @@ def install(api, replica_config, options): subject_base=subject_base, pkcs12_info=pkcs12_info, master_host=master_host, - ra_only=ra_only, promote=promote) _service.print_msg("Restarting the directory server") ds = dsinstance.DsInstance() ds.restart() + kra.enable_client_auth_to_db(paths.KRA_CS_CFG_PATH) - if not ra_only: - kra.enable_client_auth_to_db(paths.KRA_CS_CFG_PATH) - - # Restart apache for new proxy config file - services.knownservices.httpd.restart(capture_output=True) + # Restart apache for new proxy config file + services.knownservices.httpd.restart(capture_output=True) def uninstall(standalone): diff --git a/ipaserver/install/krainstance.py b/ipaserver/install/krainstance.py index b4f65fd..4735ff0 100644 --- a/ipaserver/install/krainstance.py +++ b/ipaserver/install/krainstance.py @@ -76,7 +76,7 @@ def __init__(self, realm): def configure_instance(self, realm_name, host_name, dm_password, admin_password, pkcs12_info=None, master_host=None, - subject_base=None, ra_only=False, promote=False): + subject_base=None, promote=False): """Create a KRA instance. To create a clone, pass in pkcs12_info. @@ -96,38 +96,37 @@ def configure_instance(self, realm_name, host_name, dm_password, self.realm = realm_name self.suffix = ipautil.realm_to_suffix(realm_name) - if not ra_only: - # 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(self.realm, certs.NSS_DIR) - if not ca.is_installed(): - raise RuntimeError( - "KRA configuration failed. " - "A Dogtag CA must be installed first") - - if promote: - self.step("creating installation admin user", self.setup_admin) - self.step("configuring KRA instance", self.__spawn_instance) - if not self.clone: - self.step("create KRA agent", - self.__create_kra_agent) - if not ra_only: - if promote: - self.step("destroying installation admin user", self.teardown_admin) - self.step("restarting KRA", self.restart_instance) - self.step("configure certmonger for renewals", - self.configure_certmonger_renewal) - self.step("configure certificate renewals", self.configure_renewal) - self.step("configure HTTP to proxy connections", - self.http_proxy) - if not self.clone: - self.step("add vault container", self.__add_vault_container) - self.step("apply LDAP updates", self.__apply_updates) - - self.step("enabling KRA instance", self.__enable_instance) + # 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(self.realm, certs.NSS_DIR) + if not ca.is_installed(): + raise RuntimeError( + "KRA configuration failed. " + "A Dogtag CA must be installed first") + + if promote: + self.step("creating installation admin user", self.setup_admin) + self.step("configuring KRA instance", self.__spawn_instance) + if not self.clone: + self.step("create KRA agent", + self.__create_kra_agent) + if promote: + self.step("destroying installation admin user", + self.teardown_admin) + self.step("restarting KRA", self.restart_instance) + self.step("configure certmonger for renewals", + self.configure_certmonger_renewal) + self.step("configure certificate renewals", self.configure_renewal) + self.step("configure HTTP to proxy connections", + self.http_proxy) + if not self.clone: + self.step("add vault container", self.__add_vault_container) + self.step("apply LDAP updates", self.__apply_updates) + + self.step("enabling KRA instance", self.__enable_instance) self.start_creation(runtime=126) From 1f5c5cea0e104d15942d21fc340dd20410796139 Mon Sep 17 00:00:00 2001 From: Stanislav Laznicka <slazn...@redhat.com> Date: Tue, 3 Jan 2017 09:49:48 +0100 Subject: [PATCH 4/7] Move publishing of CA cert to cainstance creation on master IPAHTTPSConnection which is set up first time in certificate profiles migration to LDAP requires CA cert to be stored in a file. https://fedorahosted.org/freeipa/ticket/5695 --- ipaserver/install/cainstance.py | 2 ++ ipaserver/install/server/install.py | 6 +----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py index e43adea..42d3d28 100644 --- a/ipaserver/install/cainstance.py +++ b/ipaserver/install/cainstance.py @@ -422,6 +422,8 @@ def configure_instance(self, host_name, dm_password, admin_password, self.step("setting audit signing renewal to 2 years", self.set_audit_renewal) self.step("restarting certificate server", self.restart_instance) if not self.clone: + self.step("publishing the CA certificate", + lambda: self.publish_ca_cert(paths.IPA_CA_CRT)) self.step("adding RA agent as a trusted user", self.__create_ca_agent) self.step("authorizing RA to modify profiles", configure_profiles_acl) self.step("authorizing RA to manage lightweight CAs", diff --git a/ipaserver/install/server/install.py b/ipaserver/install/server/install.py index fc319d9..03e5778 100644 --- a/ipaserver/install/server/install.py +++ b/ipaserver/install/server/install.py @@ -32,7 +32,7 @@ ) import ipaclient.install.ntpconf from ipaserver.install import ( - bindinstance, ca, cainstance, certs, dns, dsinstance, + bindinstance, ca, certs, dns, dsinstance, httpinstance, installutils, kra, krbinstance, memcacheinstance, ntpinstance, otpdinstance, custodiainstance, replication, service, sysupgrade) @@ -785,10 +785,6 @@ def install(installer): write_cache(cache_vars) ca.install_step_0(False, None, options) - - # Now put the CA cert where other instances exepct it - ca_instance = cainstance.CAInstance(realm_name, certs.NSS_DIR) - ca_instance.publish_ca_cert(paths.IPA_CA_CRT) else: # Put the CA cert where other instances expect it x509.write_certificate(http_ca_cert, paths.IPA_CA_CRT) From 00a323d285a77d643f982464a029fdf0e8f377bb Mon Sep 17 00:00:00 2001 From: Stanislav Laznicka <slazn...@redhat.com> Date: Tue, 20 Dec 2016 10:23:47 +0100 Subject: [PATCH 5/7] Remove NSSConnection from Dogtag Replaced NSSConnection with Python's httplib.HTTPSConnection. This class is OpenSSL-based. A client certificate with a private key is required to authenticate against the certificate server. We facilitate the RA_AGENT_PEM which already exists. https://fedorahosted.org/freeipa/ticket/5695 --- ipapython/dogtag.py | 20 +++++++------------- ipaserver/install/cainstance.py | 6 ------ ipaserver/plugins/dogtag.py | 39 +++++++++++++-------------------------- 3 files changed, 20 insertions(+), 45 deletions(-) diff --git a/ipapython/dogtag.py b/ipapython/dogtag.py index eb1f73e..ac757d5 100644 --- a/ipapython/dogtag.py +++ b/ipapython/dogtag.py @@ -20,16 +20,16 @@ import collections import xml.dom.minidom -import nss.nss as nss import six # pylint: disable=import-error from six.moves.urllib.parse import urlencode # pylint: enable=import-error from ipalib import api, errors +from ipalib.util import IPAHTTPSConnection from ipalib.errors import NetworkError from ipalib.text import _ -from ipapython import nsslib, ipautil +from ipapython import ipautil from ipapython.ipa_log_manager import root_logger # Python 3 rename. The package is available in "six.moves.http_client", but @@ -131,8 +131,8 @@ def ca_status(ca_host=None): return _parse_ca_status(body) -def https_request(host, port, url, secdir, password, nickname, - method='POST', headers=None, body=None, **kw): +def https_request(host, port, url, cafile, client_certfile, + method='POST', headers=None, body=None, **kw): """ :param method: HTTP request method (defalut: 'POST') :param url: The path (not complete URL!) to post to. @@ -145,15 +145,9 @@ def https_request(host, port, url, secdir, password, nickname, """ def connection_factory(host, port): - no_init = secdir == nsslib.current_dbdir - conn = nsslib.NSSConnection(host, port, dbdir=secdir, no_init=no_init, - tls_version_min=api.env.tls_version_min, - tls_version_max=api.env.tls_version_max) - conn.set_debuglevel(0) - conn.connect() - conn.sock.set_client_auth_data_callback( - nsslib.client_auth_data_callback, - nickname, password, nss.get_default_certdb()) + conn = IPAHTTPSConnection(host, port, cafile, client_certfile, + tls_version_min=api.env.tls_version_min, + tls_version_max=api.env.tls_version_max) return conn if body is None: diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py index 42d3d28..417ceb5 100644 --- a/ipaserver/install/cainstance.py +++ b/ipaserver/install/cainstance.py @@ -1598,7 +1598,6 @@ def import_included_profiles(): cn=['certprofiles'], ) - api.Backend.ra_certprofile._read_password() api.Backend.ra_certprofile.override_port = 8443 for (profile_id, desc, store_issued) in dogtag.INCLUDED_PROFILES: @@ -1635,7 +1634,6 @@ def repair_profile_caIPAserviceCert(): This function detects and repairs occurrences of this problem. """ - api.Backend.ra_certprofile._read_password() api.Backend.ra_certprofile.override_port = 8443 profile_id = 'caIPAserviceCert' @@ -1678,8 +1676,6 @@ def migrate_profiles_to_ldap(): """ ensure_ldap_profiles_container() - - api.Backend.ra_certprofile._read_password() api.Backend.ra_certprofile.override_port = 8443 with open(paths.CA_CS_CFG_PATH) as f: @@ -1764,8 +1760,6 @@ def ensure_ipa_authority_entry(): """ # find out authority id, issuer DN and subject DN of IPA CA - # - api.Backend.ra_lightweight_ca._read_password() api.Backend.ra_lightweight_ca.override_port = 8443 with api.Backend.ra_lightweight_ca as lwca: data = lwca.read_ca('host-authority') diff --git a/ipaserver/plugins/dogtag.py b/ipaserver/plugins/dogtag.py index 40b897d..91da3a3 100644 --- a/ipaserver/plugins/dogtag.py +++ b/ipaserver/plugins/dogtag.py @@ -1238,29 +1238,18 @@ def _parse_dogtag_error(body): def __init__(self, api): if api.env.in_tree: - self.sec_dir = api.env.dot_ipa + os.sep + 'alias' - self.pwd_file = self.sec_dir + os.sep + '.pwd' + self.ca_cert = os.path.join(api.env.dot_ipa, 'ca.crt') + self.client_certfile = os.path.join( + api.env.dot_ipa, 'ra-agent.pem') else: - self.sec_dir = paths.HTTPD_ALIAS_DIR - self.pwd_file = paths.ALIAS_PWDFILE_TXT - self.noise_file = self.sec_dir + os.sep + '.noise' - self.ipa_key_size = "2048" - self.ipa_certificate_nickname = "ipaCert" - self.ca_certificate_nickname = "caCert" - self._read_password() + self.ca_cert = paths.IPA_CA_CRT + self.client_certfile = paths.RA_AGENT_PEM super(RestClient, self).__init__(api) # session cookie self.override_port = None self.cookie = None - def _read_password(self): - try: - with open(self.pwd_file) as f: - self.password = f.readline().strip() - except IOError: - self.password = '' - @cachedproperty def ca_host(self): """ @@ -1287,9 +1276,8 @@ def __enter__(self): return status, resp_headers, _resp_body = dogtag.https_request( self.ca_host, self.override_port or self.env.ca_agent_port, - '/ca/rest/account/login', - self.sec_dir, self.password, self.ipa_certificate_nickname, - method='GET' + '/ca/rest/account/login', self.ca_cert, + self.client_certfile, 'GET' ) cookies = ipapython.cookie.Cookie.parse(resp_headers.get('set-cookie', '')) if status != 200 or len(cookies) == 0: @@ -1301,9 +1289,8 @@ def __exit__(self, exc_type, exc_value, traceback): """Log out of the REST API""" dogtag.https_request( self.ca_host, self.override_port or self.env.ca_agent_port, - '/ca/rest/account/logout', - self.sec_dir, self.password, self.ipa_certificate_nickname, - method='GET' + '/ca/rest/account/logout', self.ca_cert, + self.client_certfile, 'GET' ) self.cookie = None @@ -1343,9 +1330,8 @@ def _ssldo(self, method, path, headers=None, body=None, use_session=True): # perform main request status, resp_headers, resp_body = dogtag.https_request( self.ca_host, self.override_port or self.env.ca_agent_port, - resource, - self.sec_dir, self.password, self.ipa_certificate_nickname, - method=method, headers=headers, body=body + resource, self.ca_cert, self.client_certfile, + method, headers, body ) if status < 200 or status >= 300: explanation = self._parse_dogtag_error(resp_body) or '' @@ -1425,7 +1411,8 @@ def _sslget(self, url, port, **kw): Perform an HTTPS request """ - return dogtag.https_request(self.ca_host, port, url, self.sec_dir, self.password, self.ipa_certificate_nickname, **kw) + return dogtag.https_request(self.ca_host, port, url, self.ca_cert, + self.client_certfile, **kw) def get_parse_result_xml(self, xml_text, parse_func): ''' From a71182f780757e221c1f1b721a1a9f87faa38f23 Mon Sep 17 00:00:00 2001 From: Stanislav Laznicka <slazn...@redhat.com> Date: Tue, 3 Jan 2017 13:31:01 +0100 Subject: [PATCH 6/7] Remove NSSConnection from otptoken plugin Replace NSSConnection with IPAHTTPSConnection to be able to remove NSSConnection for good. https://fedorahosted.org/freeipa/ticket/5695 --- ipaclient/plugins/otptoken.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/ipaclient/plugins/otptoken.py b/ipaclient/plugins/otptoken.py index 885a612..1d57527 100644 --- a/ipaclient/plugins/otptoken.py +++ b/ipaclient/plugins/otptoken.py @@ -25,8 +25,8 @@ from ipalib.messages import add_message, ResultFormattingError from ipalib.plugable import Registry from ipalib.frontend import Local +from ipalib.util import IPAHTTPSConnection from ipapython.dn import DN -from ipapython.nsslib import NSSConnection from ipapython.version import API_VERSION import locale @@ -126,9 +126,7 @@ def __init__(self, **kwargs): def __inner(self, host, **kwargs): tmp = self.__kwargs.copy() tmp.update(kwargs) - # NSSConnection doesn't support timeout argument - tmp.pop('timeout', None) - return NSSConnection(host, **tmp) + return IPAHTTPSConnection(host, **tmp) def https_open(self, req): # pylint: disable=no-member @@ -173,9 +171,10 @@ def forward(self, *args, **kwargs): # Sync the token. # pylint: disable=E1101 - handler = HTTPSHandler(dbdir=api.env.nss_dir, - tls_version_min=api.env.tls_version_min, - tls_version_max=api.env.tls_version_max) + handler = HTTPSHandler( + cafile=api.env.ca_certfile, + tls_version_min=api.env.tls_version_min, + tls_version_max=api.env.tls_version_max) rsp = urllib.request.build_opener(handler).open(sync_uri, query) if rsp.getcode() == 200: status['result'][self.header] = rsp.info().get(self.header, 'unknown') From d42363098f52ca00dff1386932c3ef3a97b5da3c Mon Sep 17 00:00:00 2001 From: Stanislav Laznicka <slazn...@redhat.com> Date: Wed, 4 Jan 2017 08:47:59 +0100 Subject: [PATCH 7/7] Remove ipapython.nsslib as it is not used anymore Previous changes allowed the removal of nsslib. So long, and thanks for all the fish. https://fedorahosted.org/freeipa/ticket/5695 --- ipapython/nsslib.py | 287 ---------------------------------------------------- 1 file changed, 287 deletions(-) delete mode 100644 ipapython/nsslib.py diff --git a/ipapython/nsslib.py b/ipapython/nsslib.py deleted file mode 100644 index 08d05fc..0000000 --- a/ipapython/nsslib.py +++ /dev/null @@ -1,287 +0,0 @@ -# Authors: Rob Crittenden <rcrit...@redhat.com> -# John Dennis <jden...@redhat.com> -# -# Copyright (C) 2009 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 __future__ import print_function - -import getpass -import socket -from ipapython.ipa_log_manager import root_logger - -from nss.error import NSPRError -import nss.io as io -import nss.nss as nss -import nss.ssl as ssl -import nss.error as error - -# Python 3 rename. The package is available in "six.moves.http_client", but -# pylint cannot handle classes from that alias -try: - import httplib -except ImportError: - # pylint: disable=import-error - import http.client as httplib - -# NSS database currently open -current_dbdir = None - -def auth_certificate_callback(sock, check_sig, is_server, certdb): - cert_is_valid = False - - cert = sock.get_peer_certificate() - - pin_args = sock.get_pkcs11_pin_arg() - if pin_args is None: - pin_args = () - - # Define how the cert is being used based upon the is_server flag. This may - # seem backwards, but isn't. If we're a server we're trying to validate a - # client cert. If we're a client we're trying to validate a server cert. - if is_server: - intended_usage = nss.certificateUsageSSLClient - else: - intended_usage = nss.certificateUsageSSLServer - - try: - # If the cert fails validation it will raise an exception, the errno attribute - # will be set to the error code matching the reason why the validation failed - # and the strerror attribute will contain a string describing the reason. - approved_usage = cert.verify_now(certdb, check_sig, intended_usage, *pin_args) - except Exception as e: - root_logger.error( - 'cert validation failed for "%s" (%s)', cert.subject, - e.strerror) # pylint: disable=no-member - cert_is_valid = False - return cert_is_valid - - root_logger.debug("approved_usage = %s intended_usage = %s", - ', '.join(nss.cert_usage_flags(approved_usage)), - ', '.join(nss.cert_usage_flags(intended_usage))) - - # Is the intended usage a proper subset of the approved usage - cert_is_valid = bool(approved_usage & intended_usage) - - # If this is a server, we're finished - if is_server or not cert_is_valid: - root_logger.debug('cert valid %s for "%s"', cert_is_valid, cert.subject) - return cert_is_valid - - # Certificate is OK. Since this is the client side of an SSL - # connection, we need to verify that the name field in the cert - # matches the desired hostname. This is our defense against - # man-in-the-middle attacks. - - hostname = sock.get_hostname() - try: - # If the cert fails validation it will raise an exception - cert_is_valid = cert.verify_hostname(hostname) - except Exception as e: - root_logger.error('failed verifying socket hostname "%s" matches cert subject "%s" (%s)', - hostname, cert.subject, - e.strerror) # pylint: disable=no-member - cert_is_valid = False - return cert_is_valid - - root_logger.debug('cert valid %s for "%s"', cert_is_valid, cert.subject) - return cert_is_valid - -def client_auth_data_callback(ca_names, chosen_nickname, password, certdb): - cert = None - if chosen_nickname: - try: - cert = nss.find_cert_from_nickname(chosen_nickname, password) - priv_key = nss.find_key_by_any_cert(cert, password) - return cert, priv_key - except NSPRError: - return False - else: - nicknames = nss.get_cert_nicknames(certdb, nss.SEC_CERT_NICKNAMES_USER) - for nickname in nicknames: - try: - cert = nss.find_cert_from_nickname(nickname, password) - if cert.check_valid_times(): - if cert.has_signer_in_ca_names(ca_names): - priv_key = nss.find_key_by_any_cert(cert, password) - return cert, priv_key - except NSPRError: - return False - return False - -_af_dict = { - socket.AF_INET: io.PR_AF_INET, - socket.AF_INET6: io.PR_AF_INET6, - socket.AF_UNSPEC: io.PR_AF_UNSPEC -} - -class NSSAddressFamilyFallback(object): - def __init__(self, family): - self.sock_family = family - self.family = self._get_nss_family(self.sock_family) - - def _get_nss_family(self, sock_family): - """ - Translate a family from python socket module to nss family. - """ - try: - return _af_dict[sock_family] - except KeyError: - raise ValueError('Uknown socket family %d\n', sock_family) - - def _create_socket(self): - self.sock = io.Socket(family=self.family) - - def connect_socket(self, host, port): - try: - addr_info = io.AddrInfo(host, family=self.family) - except Exception: - raise NSPRError( - error_code=error.PR_ADDRESS_NOT_SUPPORTED_ERROR, - error_message="Cannot resolve %s using family %s" % (host, - io.addr_family_name(self.family))) - - for net_addr in addr_info: - root_logger.debug("Connecting: %s", net_addr) - net_addr.port = port - self.family = net_addr.family - try: - self._create_socket() - self.sock.connect(net_addr) - return - except Exception as e: - root_logger.debug("Could not connect socket to %s, error: %s", - net_addr, str(e)) - root_logger.debug("Try to continue with next family...") - continue - - raise NSPRError( - error_code=error.PR_ADDRESS_NOT_SUPPORTED_ERROR, - error_message="Could not connect to %s using any address" % host) - - -class NSSConnection(httplib.HTTPConnection, NSSAddressFamilyFallback): - default_port = httplib.HTTPSConnection.default_port - - def __init__(self, host, port=None, strict=None, - dbdir=None, family=socket.AF_UNSPEC, no_init=False, - tls_version_min='tls1.1', tls_version_max='tls1.2'): - """ - :param host: the server to connect to - :param port: the port to use (default is set in HTTPConnection) - :param dbdir: the NSS database directory - :param family: network family to use (default AF_UNSPEC) - :param no_init: do not initialize the NSS database. This requires - that the database has already been initialized or - the request will fail. - :param tls_min_version: mininum version of SSL/TLS supported - :param tls_max_version: maximum version of SSL/TLS supported. - """ - httplib.HTTPConnection.__init__(self, host, port, strict) - NSSAddressFamilyFallback.__init__(self, family) - - root_logger.debug('%s init %s', self.__class__.__name__, host) - - # If initialization is requested, initialize the new database. - if not no_init: - - if nss.nss_is_initialized(): - ssl.clear_session_cache() - try: - nss.nss_shutdown() - except NSPRError as e: - if e.errno != error.SEC_ERROR_NOT_INITIALIZED: - raise e - - if not dbdir: - raise RuntimeError("dbdir is required") - - nss.nss_init(dbdir) - - global current_dbdir - current_dbdir = dbdir - - ssl.set_domestic_policy() - nss.set_password_callback(self.password_callback) - self.tls_version_min = str(tls_version_min) - self.tls_version_max = str(tls_version_max) - - def _create_socket(self): - ssl_enable_renegotiation = getattr( - ssl, 'SSL_ENABLE_RENEGOTIATION', 20) - ssl_require_safe_negotiation = getattr( - ssl,'SSL_REQUIRE_SAFE_NEGOTIATION', 21) - ssl_renegotiate_requires_xtn = getattr( - ssl, 'SSL_RENEGOTIATE_REQUIRES_XTN', 2) - - # Create the socket here so we can do things like let the caller - # override the NSS callbacks - self.sock = ssl.SSLSocket(family=self.family) - self.sock.set_ssl_option(ssl.SSL_SECURITY, True) - self.sock.set_ssl_option(ssl.SSL_HANDSHAKE_AS_CLIENT, True) - try: - self.sock.set_ssl_version_range(self.tls_version_min, self.tls_version_max) - except NSPRError: - root_logger.error('Failed to set TLS range to %s, %s' % (self.tls_version_min, self.tls_version_max)) - raise - self.sock.set_ssl_option(ssl_require_safe_negotiation, False) - self.sock.set_ssl_option(ssl_enable_renegotiation, ssl_renegotiate_requires_xtn) - # Provide a callback which notifies us when the SSL handshake is complete - self.sock.set_handshake_callback(self.handshake_callback) - - # Provide a callback to verify the servers certificate - self.sock.set_auth_certificate_callback(auth_certificate_callback, - nss.get_default_certdb()) - self.sock.set_hostname(self.host) - - def password_callback(self, slot, retry, password): - if not retry and password: return password - return getpass.getpass("Enter password for %s: " % slot.token_name) - - def handshake_callback(self, sock): - """ - Verify callback. If we get here then the certificate is ok. - """ - channel = sock.get_ssl_channel_info() - suite = ssl.get_cipher_suite_info(channel.cipher_suite) - root_logger.debug("handshake complete, peer = %s", sock.get_peer_name()) - root_logger.debug('Protocol: %s' % channel.protocol_version_str.upper()) - root_logger.debug('Cipher: %s' % suite.cipher_suite_name) - - def connect(self): - self.connect_socket(self.host, self.port) - - def close(self): - """Close the connection to the HTTP server.""" - if self.sock: - self.sock.close() # close it manually... there may be other refs - self.sock = None - ssl.clear_session_cache() - - def endheaders(self, message=None): - """ - Explicitly close the connection if an error is returned after the - headers are sent. This will likely mean the initial SSL handshake - failed. If this isn't done then the connection is never closed and - subsequent NSS activities will fail with a BUSY error. - """ - try: - # FIXME: httplib uses old-style classes so super doesn't work - httplib.HTTPConnection.endheaders(self, message) - except NSPRError as e: - self.close() - raise e
-- Manage your subscription for the Freeipa-devel mailing list: https://www.redhat.com/mailman/listinfo/freeipa-devel Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code