From f29806446e58957c836f67021e51c0598a0688db Mon Sep 17 00:00:00 2001 From: John Dennis <jdennis@redhat.com> Date: Thu, 15 Nov 2012 14:57:52 -0500 Subject: [PATCH 2/3] Use secure method to acquire IPA CA certificate Major changes ipa-client-install: * Use GSSAPI connection to LDAP server to download CA cert (now the default method) * Add --ca-cert-file option to load the CA cert from a disk file. Validate the file. If this option is used the supplied CA cert is considered definitive. * The insecure HTTP retrieval method is still supported but it must be explicitly forced and a warning will be emitted. * Remain backward compatible with unattended case (except for aberrant condition when preexisting /etc/ipa/ca.crt differs from securely obtained CA cert, see below) * If /etc/ipa/ca.crt CA cert preexists the validate it matches the securely acquired CA cert, if not: - If --unattended and not --force abort with error - If interactive query user to accept new CA cert, if not abort In either case warn user. * If interactive and LDAP retrieval fails prompt user if they want to proceed with insecure HTTP method * If not interactive and LDAP retrieval fails abort unless --force * Backup preexisting /etc/ipa/ca.crt in FileStore prior to execution, if ipa-client-install fails it will be restored. Other changes: * ipadiscovery no longer retrieves CA cert via HTTP. * Add new exception class CertificateInvalidError * Add utility convert_ldap_error() to ipalib.ipautil * Replace all hardcoded instances of /etc/ipa/ca.crt in ipa-client-install with CACERT constant (matches existing practice elsewhere). Signed-off-by: Simo Sorce <simo@redhat.com> --- ipa-client/ipa-install/ipa-client-install | 404 +++++++++++++++++++++++++++-- ipa-client/ipaclient/ipadiscovery.py | 131 ++++------ ipa-client/man/ipa-client-install.1 | 8 + ipalib/errors.py | 22 ++- ipapython/ipautil.py | 36 +++ ipaserver/install/certs.py | 5 +- 6 files changed, 506 insertions(+), 100 deletions(-) diff --git a/ipa-client/ipa-install/ipa-client-install b/ipa-client/ipa-install/ipa-client-install index 3a75bfd0fde82258130cffba0c0ce32434142b14..5bba4e3a3751029ebe2b10c359afaf56bb1c8f97 100755 --- a/ipa-client/ipa-install/ipa-client-install +++ b/ipa-client/ipa-install/ipa-client-install @@ -29,22 +29,28 @@ try: import os import time import socket + import ldap + import ldap.sasl + import urlparse + import base64 import logging import tempfile import getpass from ipaclient import ipadiscovery import ipaclient.ipachangeconf import ipaclient.ntpconf - from ipapython.ipautil import run, user_input, CalledProcessError, file_exists, realm_to_suffix + from ipapython.ipautil import run, user_input, CalledProcessError, file_exists, realm_to_suffix, convert_ldap_error import ipapython.services as ipaservices from ipapython import ipautil from ipapython import dnsclient from ipapython import sysrestore from ipapython import version from ipapython import certmonger + from ipapython import errors from ipapython.config import IPAOptionParser from ConfigParser import RawConfigParser - from optparse import SUPPRESS_HELP, OptionGroup + from optparse import SUPPRESS_HELP, OptionGroup, OptionValueError + from OpenSSL import crypto except ImportError: print >> sys.stderr, """\ There was a problem importing one of the required Python modules. The @@ -54,14 +60,31 @@ error was: """ % sys.exc_value sys.exit(1) +SUCCESS = 0 CLIENT_INSTALL_ERROR = 1 CLIENT_NOT_CONFIGURED = 2 CLIENT_ALREADY_CONFIGURED = 3 CLIENT_UNINSTALL_ERROR = 4 # error after restoring files/state +CACERT = "/etc/ipa/ca.crt" client_nss_nickname_format = 'IPA Machine Certificate - %s' def parse_options(): + def validate_ca_cert_file_option(option, opt, value, parser): + if not os.path.exists(value): + raise OptionValueError("%s option '%s' does not exist" % (opt, value)) + if not os.path.isfile(value): + raise OptionValueError("%s option '%s' is not a file" % (opt, value)) + if not os.path.isabs(value): + raise OptionValueError("%s option '%s' is not an absolute file path" % (opt, value)) + + try: + cert = load_certificate_from_file(value) + except Exception, e: + raise OptionValueError("%s option '%s' is not a valid certificate file" % (opt, value)) + + parser.values.ca_cert_file = value + parser = IPAOptionParser(version=version.VERSION) basic_group = OptionGroup(parser, "basic options") @@ -92,6 +115,9 @@ def parse_options(): basic_group.add_option("-U", "--unattended", dest="unattended", action="store_true", help="unattended (un)installation never prompts the user") + basic_group.add_option("--ca-cert-file", dest="ca_cert_file", + type="string", action="callback", callback=validate_ca_cert_file_option, + help="load the CA certificate from this file") # --on-master is used in ipa-server-install and ipa-replica-install # only, it isn't meant to be used on clients. basic_group.add_option("--on-master", dest="on_master", action="store_true", @@ -167,6 +193,49 @@ def nickname_exists(nickname): else: return False +def load_certificate_from_file(filename): + """ + Using PyOpenSSL load a PEM certificate file and return an x509 object. + """ + fd = open(filename, 'r') + data = fd.read() + fd.close() + + return crypto.load_certificate(crypto.FILETYPE_PEM, data) + +def make_pem(data): + """ + Convert a raw base64-encoded blob into something that looks like a PE + file with lines split to 64 characters and proper headers. + """ + pemcert = '\n'.join([data[x:x+64] for x in range(0, len(data), 64)]) + return '-----BEGIN CERTIFICATE-----\n' + \ + pemcert + \ + '\n-----END CERTIFICATE-----' + +def write_certificate(dercert, filename): + """ + Write the certificate to a file in PEM format. + + The incoming cert value must be DER-encoded. + """ + try: + fp = open(filename, 'w') + fp.write(make_pem(base64.b64encode(dercert))) + fp.close() + except (IOError, OSError), e: + raise errors.FileError(reason=str(e)) + +def cert_summary(msg, cert, indent=' '): + if msg: + s = '%s\n' % msg + else: + s = '' + s += '%sSubject: %s\n' % (indent, str(cert.get_subject())[18:-2]) + s += '%sIssuer: %s\n' % (indent, str(cert.get_issuer())[18:-2]) + + return s + def emit_quiet(quiet, message): if not quiet: print message @@ -586,7 +655,7 @@ def configure_krb5_conf(fstore, cli_basedn, cli_realm, cli_domain, cli_server, c {'name':'default_domain', 'type':'option', 'value':cli_domain}] else: kropts = [] - kropts.append({'name':'pkinit_anchors', 'type':'option', 'value':'FILE:/etc/ipa/ca.crt'}) + kropts.append({'name':'pkinit_anchors', 'type':'option', 'value':'FILE:%s' % CACERT}) ropts = [{'name':cli_realm, 'type':'subsection', 'value':kropts}] opts.append({'name':'realms', 'type':'section', 'value':ropts}) @@ -730,7 +799,7 @@ def configure_sssd_conf(fstore, cli_realm, cli_domain, cli_server, options): # Note that SSSD will force StartTLS because the channel is later used for # authentication as well if password migration is enabled. Thus set the option # unconditionally. - domain.set_option('ldap_tls_cacert', '/etc/ipa/ca.crt') + domain.set_option('ldap_tls_cacert', CACERT) if options.dns_updates: domain.set_option('ipa_dyndns_update', True) @@ -849,6 +918,308 @@ def client_dns(server, hostname, dns_updates=False): if dns_updates or not dns_ok: update_dns(server, hostname) +def get_ca_cert_from_file(url): + ''' + Get the CA cert from a user supplied file and write it into the + CACERT file. + + Raises errors.NoCertificateError if unable to read cert. + Raises errors.FileError if unable to write cert. + ''' + + # pylint: disable=E1101 + try: + (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url, 'file') + except Exception, e: + raise errors.FileError("unable to parse file url '%s'" % (url)) + + if scheme != 'file': + raise errors.FileError("url is not a file scheme '%s'" % (url)) + + filename = path + + if not os.path.exists(filename): + raise errors.FileError("file '%s' does not exist" % (filename)) + + if not os.path.isfile(filename): + raise errors.FileError("file '%s' is not a file" % (filename)) + + logging.debug("trying to retrieve CA cert from file %s", filename) + try: + cert = load_certificate_from_file(filename) + except Exception, e: + raise errors.NoCertificateError(entry=filename) + + try: + write_certificate(crypto.dump_certificate(crypto.FILETYPE_ASN1, cert), CACERT) + except Exception, e: + raise errors.FileError(reason = + u"cannot write certificate file '%s': %s" % (CACERT, e)) + +def get_ca_cert_from_http(url, ca_file, warn=True): + ''' + Use HTTP to retrieve the CA cert and write it into the CACERT file. + This is insecure and should be avoided. + + Raises errors.NoCertificateError if unable to retrieve and write cert. + ''' + + if warn: + logging.warning("Downloading the CA certificate via HTTP, " + + "this is INSECURE") + + logging.debug("trying to retrieve CA cert via HTTP from %s", url) + try: + + run(["/usr/bin/wget", "-O", ca_file, url]) + except CalledProcessError, e: + raise errors.NoCertificateError(entry=url) + +def get_ca_cert_from_ldap(url, basedn, ca_file): + ''' + Retrieve th CA cert from the LDAP server by binding to the + server with GSSAPI using the current Kerberos credentials. + Write the retrieved cert into the CACERT file. + + Raises errors.NoCertificateError if cert is not found. + Raises errors.NetworkError if LDAP connection can't be established. + Raises errors.LDAPError for any other generic LDAP error. + Raises errors.OnlyOneValueAllowed if more than one cert is found. + Raises errors.FileError if unable to write cert. + ''' + + ca_cert_attr = 'cAcertificate;binary' + dn = 'CN=CAcert, CN=ipa, CN=etc, %s' % basedn + + SASL_GSSAPI = ldap.sasl.sasl({},'GSSAPI') + + logging.debug("trying to retrieve CA cert via LDAP from %s", url) + + conn = ldap.initialize(url) + try: + conn.sasl_interactive_bind_s('', SASL_GSSAPI) + result = conn.search_st(str(dn), ldap.SCOPE_BASE, 'objectclass=pkiCA', + [ca_cert_attr], timeout=10) + except ldap.NO_SUCH_OBJECT, e: + logging.debug("get_ca_cert_from_ldap() error: %s", + convert_ldap_error(e)) + raise errors.NoCertificateError(entry=url) + + except ldap.SERVER_DOWN, e: + logging.debug("get_ca_cert_from_ldap() error: %s", + convert_ldap_error(e)) + raise errors.NetworkError(uri=url, error=str(e)) + except Exception, e: + logging.debug("get_ca_cert_from_ldap() error: %s", + convert_ldap_error(e)) + raise errors.LDAPError(str(e)) + + if len(result) != 1: + raise errors.OnlyOneValueAllowed(attr=ca_cert_attr) + + attrs = result[0][1] + try: + der_cert = attrs[ca_cert_attr][0] + except KeyError: + raise errors.NoCertificateError(entry=ca_cert_attr) + + try: + write_certificate(der_cert, ca_file) + except Exception, e: + raise errors.FileError(reason = + u"cannot write certificate file '%s': %s" % (ca_file, e)) + +def validate_new_ca_cert(existing_ca_cert, ca_file, ask, override=False): + + try: + new_ca_cert = load_certificate_from_file(ca_file) + except Exception, e: + raise errors.FileError( + "Unable to read new ca cert '%s': %s" % (ca_file, e)) + + if existing_ca_cert is None: + logging.info( + cert_summary("Successfully retrieved CA cert", new_ca_cert)) + return + + if crypto.dump_certificate(crypto.FILETYPE_ASN1, existing_ca_cert) != crypto.dump_certificate(crypto.FILETYPE_ASN1, existing_ca_cert): + logging.warning( + "The CA cert available from the IPA server does not match the\n" + "local certificate available at %s" % CACERT) + logging.warning( + cert_summary("Existing CA cert:", existing_ca_cert)) + logging.warning( + cert_summary("Retrieved CA cert:", new_ca_cert)) + if override: + logging.warning("Overriding existing CA cert\n") + elif not ask or not user_input( + "Do you want to replace the local certificate with the CA\n" + "certificate retrieved from the IPA server?", True): + raise errors.CertificateInvalidError(name='Retrieved CA') + else: + logging.debug( + "Existing CA cert and Retrieved CA cert are identical") + os.remove(ca_file) + + +def get_ca_cert(fstore, options, server, basedn): + ''' + Examine the different options and determine a method for obtaining + the CA cert. + + If successful the CA cert will have been written into CACERT. + + Raises errors.NoCertificateError if not successful. + + The logic for determining how to load the CA cert is as follow: + + In the OTP case (not -p and -w): + + 1. load from user supplied cert file + 2. else load from HTTP + + In the 'user_auth' case ((-p and -w) or interactive): + + 1. load from user supplied cert file + 2. load from LDAP using SASL/GSS/Krb5 auth + (provides mutual authentication, integrity and security) + 3. if LDAP failed and interactive ask for permission to + use insecure HTTP (default: No) + + In the unattended case: + + 1. load from user supplied cert file + 2. load from HTTP if --force specified else fail + + In all cases if HTTP is used emit warning message + ''' + + ca_file = CACERT + ".new" + + def ldap_url(): + return urlparse.urlunparse(('ldap', ipautil.format_netloc(server), + '', '', '', '')) + + def file_url(): + return urlparse.urlunparse(('file', '', options.ca_cert_file, + '', '', '')) + + def http_url(): + return urlparse.urlunparse(('http', ipautil.format_netloc(server), + '/ipa/config/ca.crt', '', '', '')) + + + interactive = not options.unattended + otp_auth = options.principal is None and options.password is not None + existing_ca_cert = None + + if options.ca_cert_file: + url = file_url() + try: + get_ca_cert_from_file(url) + except Exception, e: + logging.debug(e) + raise errors.NoCertificateError(entry=url) + logging.debug("CA cert provided by user, use it!") + else: + if os.path.exists(CACERT): + if os.path.isfile(CACERT): + try: + existing_ca_cert = load_certificate_from_file(CACERT) + except Exception, e: + raise errors.FileError(reason=u"Unable to load existing" + + " CA cert '%s': %s" % (CACERT, e)) + else: + raise errors.FileError(reason=u"Existing ca cert '%s' is " + + "not a plain file" % (CACERT)) + + if otp_auth: + if existing_ca_cert: + logging.info("OTP case, CA cert preexisted, use it") + else: + url = http_url() + override = not interactive + if interactive and not user_input( + "Do you want download the CA cert from " + url + " ?\n" + "(this is INSECURE)", False): + raise errors.NoCertificateError(message=u"HTTP certificate" + " download declined by user") + try: + get_ca_cert_from_http(url, ca_file, override) + except Exception, e: + logging.debug(e) + raise errors.NoCertificateError(entry=url) + + try: + validate_new_ca_cert(existing_ca_cert, ca_file, + False, override) + except Exception, e: + os.unlink(ca_file) + raise + else: + # Auth with user credentials + url = ldap_url() + try: + get_ca_cert_from_ldap(url, basedn, ca_file) + try: + validate_new_ca_cert(existing_ca_cert, + ca_file, interactive) + except Exception, e: + os.unlink(ca_file) + raise + except errors.NoCertificateError, e: + logging.debug(str(e)) + url = http_url() + if existing_ca_cert: + logging.warning( + "Unable to download CA cert from LDAP\n" + "but found preexisting cert, using it.\n") + elif interactive and not user_input( + "Unable to download CA cert from LDAP.\n" + "Do you want download the CA cert form " + url + "?\n" + "(this is INSECURE)", False): + raise errors.NoCertificateError(message=u"HTTP " + "certificate download declined by user") + elif not interactive and not options.force: + logging.error( + "In unattended mode without a One Time Password " + "(OTP) or without --ca-cert-file\nYou must specify" + " --force to retrieve the CA cert using HTTP") + raise errors.NoCertificateError(message=u"HTTP " + "certificate download requires --force") + else: + try: + get_ca_cert_from_http(url, ca_file) + except Exception, e: + logging.debug(e) + raise errors.NoCertificateError(entry=url) + try: + validate_new_ca_cert(existing_ca_cert, + ca_file, interactive) + except Exception, e: + os.unlink(ca_file) + raise + except Exception, e: + logging.debug(str(e)) + raise errors.NoCertificateError(entry=url) + + + # We should have a cert now, move it to the canonical place + if os.path.exists(ca_file): + os.rename(ca_file, CACERT) + elif existing_ca_cert is None: + raise errors.InternalError(u"expected CA cert file '%s' to " + u"exist, but it's absent" % (ca_file)) + + + # Make sure the file permissions are correct + try: + os.chmod(CACERT, 0644) + except Exception, e: + raise errors.FileError(reason=u"Unable set permissions on ca " + u"cert '%s': %s" % (CACERT, e)) + + def install(options, env, fstore, statestore): dnsok = False @@ -986,20 +1357,6 @@ def install(options, env, fstore, statestore): options.principal = user_input("User authorized to enroll computers", allow_empty=False) logging.debug("will use principal: %s\n", options.principal) - # Get the CA certificate - try: - # Remove anything already there so that wget doesn't use its - # too-clever renaming feature - os.remove("/etc/ipa/ca.crt") - except: - pass - - try: - run(["/usr/bin/wget", "-O", "/etc/ipa/ca.crt", "http://%s/ipa/config/ca.crt" % ipautil.format_netloc(cli_server)]) - except CalledProcessError, e: - print 'Retrieving CA from %s failed.\n%s' % (cli_server, str(e)) - return CLIENT_INSTALL_ERROR - if not options.on_master: nolog = tuple() # First test out the kerberos configuration @@ -1080,6 +1437,15 @@ def install(options, env, fstore, statestore): join_args.append(password) nolog = (password,) + # Get the CA certificate + try: + os.environ['KRB5_CONFIG'] = env['KRB5_CONFIG'] + get_ca_cert(fstore, options, cli_server, cli_basedn) + del os.environ['KRB5_CONFIG'] + except Exception, e: + logging.error("Cannot obtain CA certificate\n%s", e) + return CLIENT_INSTALL_ERROR + # Now join the domain (stdout, stderr, returncode) = run(join_args, raiseonerr=False, env=env, nolog=nolog) @@ -1116,7 +1482,7 @@ def install(options, env, fstore, statestore): print "Configured /etc/sssd/sssd.conf" # Add the CA to the default NSS database and trust it - run(["/usr/bin/certutil", "-A", "-d", "/etc/pki/nssdb", "-n", "IPA CA", "-t", "CT,C,C", "-a", "-i", "/etc/ipa/ca.crt"]) + run(["/usr/bin/certutil", "-A", "-d", "/etc/pki/nssdb", "-n", "IPA CA", "-t", "CT,C,C", "-a", "-i", CACERT]) # If on master assume kerberos is already configured properly. if not options.on_master: diff --git a/ipa-client/ipaclient/ipadiscovery.py b/ipa-client/ipaclient/ipadiscovery.py index 9e446b0174718ad8bf1b74495cd2f97969bbc91b..10ffea36c16ddce034fe47b6c5391a75f6e79da2 100644 --- a/ipa-client/ipaclient/ipadiscovery.py +++ b/ipa-client/ipaclient/ipadiscovery.py @@ -194,9 +194,7 @@ class IPADiscovery: def ipacheckldap(self, thost, trealm): """ Given a host and kerberos realm verify that it is an IPA LDAP - server hosting the realm. The connection is an SSL connection - so the remote IPA CA cert must be available at - http://HOST/ipa/config/ca.crt + server hosting the realm. Returns a list [errno, host, realm] or an empty list on error. Errno is an error number: @@ -214,87 +212,64 @@ class IPADiscovery: i = 0 - # Get the CA certificate - try: - # Create TempDir - temp_ca_dir = tempfile.mkdtemp() - except OSError, e: - raise RuntimeError("Creating temporary directory failed: %s" % str(e)) - - try: - run(["/usr/bin/wget", "-O", "%s/ca.crt" % temp_ca_dir, "-T", "15", "-t", "2", - "http://%s/ipa/config/ca.crt" % format_netloc(thost)]) - except CalledProcessError, e: - logging.debug('Retrieving CA from %s failed.\n%s' % (thost, str(e))) - return [NOT_IPA_SERVER] - #now verify the server is really an IPA server try: - try: - logging.debug("Init ldap with: ldap://"+format_netloc(thost, 389)) - lh = ldap.initialize("ldap://"+format_netloc(thost, 389)) - ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, True) - ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, "%s/ca.crt" % temp_ca_dir) - lh.set_option(ldap.OPT_PROTOCOL_VERSION, 3) - lh.set_option(ldap.OPT_X_TLS_DEMAND, True) - lh.start_tls_s() - lh.simple_bind_s("","") - - # get IPA base DN - logging.debug("Search LDAP server for IPA base DN") - basedn = get_ipa_basedn(lh) - - if basedn is None: - return [NOT_IPA_SERVER] - - self.basedn = basedn - - #search and return known realms - logging.debug("Search for (objectClass=krbRealmContainer) in "+self.basedn+"(sub)") - lret = lh.search_s("cn=kerberos,"+self.basedn, ldap.SCOPE_SUBTREE, "(objectClass=krbRealmContainer)") - if not lret: - #something very wrong - return [REALM_NOT_FOUND] - logging.debug("Found: "+str(lret)) - - for lres in lret: - for lattr in lres[1]: - if lattr.lower() == "cn": - lrealms.append(lres[1][lattr][0]) - - - if trealm: - for r in lrealms: - if trealm == r: - return [0, thost, trealm] - # must match or something is very wrong + logging.debug("Init ldap with: ldap://"+format_netloc(thost, 389)) + lh = ldap.initialize("ldap://"+format_netloc(thost, 389)) + lh.set_option(ldap.OPT_PROTOCOL_VERSION, 3) + lh.simple_bind_s("","") + + # get IPA base DN + logging.debug("Search LDAP server for IPA base DN") + basedn = get_ipa_basedn(lh) + + if basedn is None: + return [NOT_IPA_SERVER] + + self.basedn = basedn + + #search and return known realms + logging.debug("Search for (objectClass=krbRealmContainer) in "+self.basedn+"(sub)") + lret = lh.search_s("cn=kerberos,"+self.basedn, ldap.SCOPE_SUBTREE, "(objectClass=krbRealmContainer)") + if not lret: + #something very wrong + return [REALM_NOT_FOUND] + logging.debug("Found: "+str(lret)) + + for lres in lret: + for lattr in lres[1]: + if lattr.lower() == "cn": + lrealms.append(lres[1][lattr][0]) + + + if trealm: + for r in lrealms: + if trealm == r: + return [0, thost, trealm] + # must match or something is very wrong + return [REALM_NOT_FOUND] + else: + if len(lrealms) != 1: + #which one? we can't attach to a multi-realm server without DNS working return [REALM_NOT_FOUND] else: - if len(lrealms) != 1: - #which one? we can't attach to a multi-realm server without DNS working - return [REALM_NOT_FOUND] - else: - return [0, thost, lrealms[0]] - - #we shouldn't get here - return [UNKNOWN_ERROR] + return [0, thost, lrealms[0]] - except LDAPError, err: - if isinstance(err, ldap.TIMEOUT): - logging.error("LDAP Error: timeout") - return [NO_LDAP_SERVER] + #we shouldn't get here + return [UNKNOWN_ERROR] - if isinstance(err, ldap.INAPPROPRIATE_AUTH): - logging.debug("LDAP Error: Anonymous acces not allowed") - return [NO_ACCESS_TO_LDAP] + except LDAPError, err: + if isinstance(err, ldap.TIMEOUT): + logging.error("LDAP Error: timeout") + return [NO_LDAP_SERVER] - logging.error("LDAP Error: %s: %s" % - (err.args[0]['desc'], err.args[0].get('info', ''))) - return [UNKNOWN_ERROR] + if isinstance(err, ldap.INAPPROPRIATE_AUTH): + logging.debug("LDAP Error: Anonymous acces not allowed") + return [NO_ACCESS_TO_LDAP] - finally: - os.remove("%s/ca.crt" % temp_ca_dir) - os.rmdir(temp_ca_dir) + logging.error("LDAP Error: %s: %s" % + (err.args[0]['desc'], err.args[0].get('info', ''))) + return [UNKNOWN_ERROR] def ipadnssearchldap(self, tdomain): diff --git a/ipa-client/man/ipa-client-install.1 b/ipa-client/man/ipa-client-install.1 index 6f5e0fc4590f69163573798ae80f839179e7df77..327e83776ac6298fae2b0eb328fb3d220860156e 100644 --- a/ipa-client/man/ipa-client-install.1 +++ b/ipa-client/man/ipa-client-install.1 @@ -71,6 +71,14 @@ Print debugging information to stdout .TP \fB\-U\fR, \fB\-\-unattended\fR Unattended installation. The user will not be prompted. +.TP +\fB\-\-ca-cert-file\fR=\fICA_FILE\fR +Do not attempt to acquire the IPA CA certificate via automated means, +instead use the CA certificate found locally in in \fICA_FILE\fR. The +\fICA_FILE\fR must be an absolute path to a PEM formatted certificate +file. The CA certificate found in \fICA_FILE\fR is considered +authoritative and will be installed without checking to see if it's +valid for the IPA domain. .SS "SSSD OPTIONS" .TP diff --git a/ipalib/errors.py b/ipalib/errors.py index 3013236bbab713e2a6be24a8c229c6e9f4d51e48..36a098a138bd79363c984104a99682b78f1a9513 100644 --- a/ipalib/errors.py +++ b/ipalib/errors.py @@ -101,9 +101,11 @@ current block assignments: """ from inspect import isclass -from text import _ as ugettext, ngettext as ungettext -from constants import TYPE_ERROR +def ugettext(data): + return data + +TYPE_ERROR = '%s: need a %r; got %r (a %r)' class PrivateError(StandardError): """ @@ -1525,6 +1527,22 @@ class NotRegisteredError(ExecutionError): errno = 4306 format = _('Not registered yet') +class CertificateInvalidError(CertificateError): + """ + **4310** Raised when a certificate is not valid + + For example: + + >>> raise CertificateInvalidError(name=_(u'CA')) + Traceback (most recent call last): + ... + CertificateInvalidError: CA certificate is not valid + + """ + + errno = 4310 + format = _('%(name)s certificate is not valid') + ############################################################################## # 5000 - 5999: Generic errors diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py index e0d6cb4c5426f4e893c9ac3eaad618928448ac9b..1bf671d3d096c2b30d4fc900a3abbd942ef8d57e 100644 --- a/ipapython/ipautil.py +++ b/ipapython/ipautil.py @@ -1193,3 +1193,39 @@ def backup_config_and_replace_variables(fstore, filepath, replacevars=dict(), ap old_values = config_replace_variables(filepath, replacevars, appendvars) return old_values + +def convert_ldap_error(exc): + """ + Make LDAP exceptions prettier. + + Some LDAP exceptions have a dict with descriptive information, if + this exception has a dict extract useful information from it and + format it into something usable and return that. If the LDAP + exception does not have an information dict then return the name + of the LDAP exception. + + If the exception is not an LDAP exception then convert the + exception to a string and return that instead. + """ + if isinstance(exc, ldap.LDAPError): + name = exc.__class__.__name__ + + if len(exc.args): + d = exc.args[0] + if isinstance(d, dict): + desc = d.get('desc', '').strip() + info = d.get('info', '').strip() + if desc and info: + return '%s %s' % (desc, info) + elif desc: + return desc + elif info: + return info + else: + return name + else: + return name + else: + return name + else: + return str(exc) diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py index feac48a89c9acc961c8490ae0559619da221b041..52d4b9814f3ba063a14889e06ceb7034e1704470 100644 --- a/ipaserver/install/certs.py +++ b/ipaserver/install/certs.py @@ -226,7 +226,10 @@ class CertDB(object): self.subject_base = "O=IPA" self.subject_format = "CN=%%s,%s" % self.subject_base - self.cacert_name = get_ca_nickname(self.realm) + if self.self_signed_ca: + self.cacert_name = get_ca_nickname(self.realm, 'CN=%s Certificate Authority') + else: + self.cacert_name = get_ca_nickname(self.realm) self.valid_months = "120" self.keysize = "1024" -- 1.7.4.1