[Pkg-freeipa-devel] freeipa: Changes to 'ubuntu'

Timo Aaltonen tjaalton-guest at alioth.debian.org
Mon Feb 11 22:05:07 UTC 2013


 debian/changelog                                                          |   16 
 debian/patches/0110-Upload-CA-cert-in-the-directory-on-install.patch      |   73 
 debian/patches/0111-Update-plugin-to-upload-CA-certificate-to-LDAP.patch  |   78 
 debian/patches/0112-Do-SSL-CA-verification-and-hostname-validation.patch  |   27 
 debian/patches/0113-Use-secure-method-to-acquire-IPA-CA-certificate.patch |  798 ++++++++++
 debian/patches/check-through-all-ldap-servers.patch                       |   78 
 debian/patches/series                                                     |    5 
 7 files changed, 1075 insertions(+)

New commits:
commit 987a8672584fc153334a76ea12aca76075057375
Author: Timo Aaltonen <tjaalton at ubuntu.com>
Date:   Mon Feb 11 00:34:12 2013 +0200

    release to raring

diff --git a/debian/changelog b/debian/changelog
index cf3e6ea..0cf0a31 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,4 +1,4 @@
-freeipa (2.1.4-0ubuntu2) UNRELEASED; urgency=low
+freeipa (2.1.4-0ubuntu2) raring; urgency=low
 
   * 0110-Upload-CA-cert-in-the-directory-on-install.patch
     0111-Update-plugin-to-upload-CA-certificate-to-LDAP.patch
@@ -7,12 +7,12 @@ freeipa (2.1.4-0ubuntu2) UNRELEASED; urgency=low
     - CVE-2012-5484 - The client in FreeIPA 2.x and 3.x before 3.1.2 does
       not properly obtain the Certification Authority (CA) certificate
       from the server, which allows man-in-the-middle attackers to spoof
-      a join procedure via a crafted certificate.
+      a join procedure via a crafted certificate. (LP: #1104954)
   * check-through-all-ldap-servers.patch: Check through all LDAP servers
     in the domain during IPA discovery (ticket #1827). Patch from 2.2
     to aid in porting patch 0113.
 
- -- Timo Aaltonen <tjaalton at ubuntu.com>  Mon, 11 Feb 2013 00:13:39 +0200
+ -- Timo Aaltonen <tjaalton at ubuntu.com>  Mon, 11 Feb 2013 00:32:12 +0200
 
 freeipa (2.1.4-0ubuntu1) precise; urgency=low
 

commit f92c64be281896d712f61a14e55a20116fbe5f6b
Author: Timo Aaltonen <tjaalton at ubuntu.com>
Date:   Mon Feb 11 00:22:54 2013 +0200

    patches to fix CVE-2012-5484

diff --git a/debian/changelog b/debian/changelog
index 3debc07..cf3e6ea 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,19 @@
+freeipa (2.1.4-0ubuntu2) UNRELEASED; urgency=low
+
+  * 0110-Upload-CA-cert-in-the-directory-on-install.patch
+    0111-Update-plugin-to-upload-CA-certificate-to-LDAP.patch
+    0112-Do-SSL-CA-verification-and-hostname-validation.patch
+    0113-Use-secure-method-to-acquire-IPA-CA-certificate.patch:
+    - CVE-2012-5484 - The client in FreeIPA 2.x and 3.x before 3.1.2 does
+      not properly obtain the Certification Authority (CA) certificate
+      from the server, which allows man-in-the-middle attackers to spoof
+      a join procedure via a crafted certificate.
+  * check-through-all-ldap-servers.patch: Check through all LDAP servers
+    in the domain during IPA discovery (ticket #1827). Patch from 2.2
+    to aid in porting patch 0113.
+
+ -- Timo Aaltonen <tjaalton at ubuntu.com>  Mon, 11 Feb 2013 00:13:39 +0200
+
 freeipa (2.1.4-0ubuntu1) precise; urgency=low
 
   * Merge from unreleased debian git (LP: #259547, #958599).
diff --git a/debian/patches/0110-Upload-CA-cert-in-the-directory-on-install.patch b/debian/patches/0110-Upload-CA-cert-in-the-directory-on-install.patch
new file mode 100644
index 0000000..89af116
--- /dev/null
+++ b/debian/patches/0110-Upload-CA-cert-in-the-directory-on-install.patch
@@ -0,0 +1,73 @@
+From ef61cae3c309a5e7eaf2e61611bea10c4e23bd48 Mon Sep 17 00:00:00 2001
+From: Simo Sorce <simo at redhat.com>
+Date: Mon, 12 Nov 2012 17:43:05 -0500
+Subject: [PATCH 110/113] Upload CA cert in the directory on install
+
+This will later allow clients to securely download the CA cert by
+performaing mutual auth using LDAP with GSSAPI
+---
+ install/share/Makefile.am        |  1 +
+ install/share/upload-cacert.ldif |  7 +++++++
+ ipaserver/install/dsinstance.py  | 16 ++++++++++++++++
+ 3 files changed, 24 insertions(+)
+ create mode 100644 install/share/upload-cacert.ldif
+
+--- a/install/share/Makefile.am
++++ b/install/share/Makefile.am
+@@ -53,6 +53,7 @@ app_DATA =				\
+ 	sudobind.ldif			\
+ 	automember.ldif			\
+ 	replica-automember.ldif		\
++	upload-cacert.ldif              \
+ 	$(NULL)
+ 
+ EXTRA_DIST =				\
+--- /dev/null
++++ b/install/share/upload-cacert.ldif
+@@ -0,0 +1,7 @@
++# add CA certificate to LDAP server
++dn: cn=CAcert,cn=ipa,cn=etc,$SUFFIX
++changetype: add
++objectClass: nsContainer
++objectClass: pkiCA
++cn: CAcert
++cACertificate;binary:: $CADERCERT
+--- a/ipaserver/install/dsinstance.py
++++ b/ipaserver/install/dsinstance.py
+@@ -43,6 +43,7 @@ from ipaserver.install import httpinstan
+ from ipaserver.install import replication
+ from ipalib import util, errors
+ from ipaserver.plugins.ldap2 import ldap2
++import base64
+ 
+ SERVER_ROOT_64 = "/usr/lib64/dirsrv"
+ SERVER_ROOT_32 = "/usr/lib/dirsrv"
+@@ -246,6 +247,8 @@ class DsInstance(service.Service):
+         if hbac_allow:
+             self.step("creating default HBAC rule allow_all", self.add_hbac)
+ 
++        self.step("Upload CA cert to the directory", self.__upload_ca_cert)
++
+         self.__common_post_setup()
+ 
+         self.start_creation("Configuring directory server", 60)
+@@ -569,6 +572,19 @@ class DsInstance(service.Service):
+         # check for open secure port 636 from now on
+         self.open_ports.append(636)
+ 
++    def __upload_ca_cert(self):
++        """
++        Upload the CA certificate in DER form in the LDAP directory.
++        """
++
++        dirname = config_dirname(self.serverid)
++        certdb = certs.CertDB(self.realm_name, nssdir=dirname, subject_base=self.subject_base)
++
++        dercert = certdb.get_cert_from_db(certdb.cacert_name, pem=False)
++        self.sub_dict['CADERCERT'] = base64.b64encode(dercert)
++
++        self._ldap_mod('upload-cacert.ldif', self.sub_dict)
++
+     def __add_default_layout(self):
+         self._ldap_mod("bootstrap-template.ldif", self.sub_dict)
+ 
diff --git a/debian/patches/0111-Update-plugin-to-upload-CA-certificate-to-LDAP.patch b/debian/patches/0111-Update-plugin-to-upload-CA-certificate-to-LDAP.patch
new file mode 100644
index 0000000..b44c298
--- /dev/null
+++ b/debian/patches/0111-Update-plugin-to-upload-CA-certificate-to-LDAP.patch
@@ -0,0 +1,78 @@
+From f8ec012946034e8c8b224d8be06696a7733c677d Mon Sep 17 00:00:00 2001
+From: Alexander Bokovoy <abokovoy at redhat.com>
+Date: Mon, 12 Nov 2012 17:44:15 -0500
+Subject: [PATCH 111/113] Update plugin to upload CA certificate to LDAP
+
+Define post-update plugin to upload public CA certificate to IPA LDAP server.
+The plugin includes also update file that creates default container for the
+certificate.
+---
+ ipaserver/install/plugins/upload_cacrt.py | 56 +++++++++++++++++++++++++++++++
+ 1 file changed, 56 insertions(+)
+ create mode 100644 ipaserver/install/plugins/upload_cacrt.py
+
+diff --git a/ipaserver/install/plugins/upload_cacrt.py b/ipaserver/install/plugins/upload_cacrt.py
+new file mode 100644
+index 0000000..db99aae
+--- /dev/null
++++ b/ipaserver/install/plugins/upload_cacrt.py
+@@ -0,0 +1,56 @@
++# Authors:
++#   Alexander Bokovoy <abokovoy at redhat.com>
++#
++# Copyright (C) 2012  Red Hat
++# see file 'COPYING' for use and warranty information
++#
++# This program is free software; you can redistribute it and/or modify
++# it under the terms of the GNU General Public License as published by
++# the Free Software Foundation, either version 3 of the License, or
++# (at your option) any later version.
++#
++# This program is distributed in the hope that it will be useful,
++# but WITHOUT ANY WARRANTY; without even the implied warranty of
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++# GNU General Public License for more details.
++#
++# You should have received a copy of the GNU General Public License
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.
++
++from ipaserver.install.plugins import MIDDLE
++from ipaserver.install.plugins.baseupdate import PostUpdate
++from ipaserver.install.dsinstance import realm_to_serverid, config_dirname
++from ipaserver.install import certs
++from ipalib import api
++from ipalib.dn import DN
++import base64
++
++class update_upload_cacrt(PostUpdate):
++    """
++    Upload public CA certificate to LDAP
++    """
++    order=MIDDLE
++
++    def execute(self, **options):
++        ldap = self.obj.backend
++        (cdn, ipa_config) = ldap.get_ipa_config()
++        subject_base = ipa_config.get('ipacertificatesubjectbase', [None])[0]
++        dirname = config_dirname(realm_to_serverid(api.env.realm))
++        certdb = certs.CertDB(api.env.realm, nssdir=dirname, subject_base=subject_base)
++
++        dercert = certdb.get_cert_from_db(certdb.cacert_name, pem=False)
++        cadercert = base64.b64encode(dercert)
++
++        updates = {}
++        dn = str(DN(('cn', 'CACert'), ('cn', 'ipa'), ('cn','etc'), api.env.basedn))
++
++        cacrt_entry = ['objectclass:nsContainer',
++                       'objectclass:pkiCA',
++                       'cn:CAcert',
++                       'cACertificate;binary:%s' % cadercert,
++                      ]
++        updates[dn] = {'dn': dn, 'default': cacrt_entry}
++
++        return (False, True, [updates])
++
++api.register(update_upload_cacrt)
+-- 
+1.7.11.7
+
diff --git a/debian/patches/0112-Do-SSL-CA-verification-and-hostname-validation.patch b/debian/patches/0112-Do-SSL-CA-verification-and-hostname-validation.patch
new file mode 100644
index 0000000..7aed1d6
--- /dev/null
+++ b/debian/patches/0112-Do-SSL-CA-verification-and-hostname-validation.patch
@@ -0,0 +1,27 @@
+From 676e92e03325ebcac1c3442f548f48bb7f1ab15e Mon Sep 17 00:00:00 2001
+From: Rob Crittenden <rcritten at redhat.com>
+Date: Tue, 13 Nov 2012 17:42:07 -0500
+Subject: [PATCH 112/113] Do SSL CA verification and hostname validation.
+
+---
+ ipa-client/ipa-join.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/ipa-client/ipa-join.c b/ipa-client/ipa-join.c
+index 4c37113..6a9770f 100644
+--- a/ipa-client/ipa-join.c
++++ b/ipa-client/ipa-join.c
+@@ -166,8 +166,8 @@ callRPC(char * user_agent,
+     memset(curlXportParmsP, 0, sizeof(*curlXportParmsP));
+ 
+     /* Have curl do SSL certificate validation */
+-    curlXportParmsP->no_ssl_verifypeer = 1;
+-    curlXportParmsP->no_ssl_verifyhost = 1;
++    curlXportParmsP->no_ssl_verifypeer = 0;
++    curlXportParmsP->no_ssl_verifyhost = 0;
+     curlXportParmsP->cainfo = "/etc/ipa/ca.crt";
+     curlXportParmsP->user_agent = user_agent;
+     /* Enable GSSAPI credentials delegation */
+-- 
+1.7.11.7
+
diff --git a/debian/patches/0113-Use-secure-method-to-acquire-IPA-CA-certificate.patch b/debian/patches/0113-Use-secure-method-to-acquire-IPA-CA-certificate.patch
new file mode 100644
index 0000000..9f80c3e
--- /dev/null
+++ b/debian/patches/0113-Use-secure-method-to-acquire-IPA-CA-certificate.patch
@@ -0,0 +1,798 @@
+From 63ff5f04c2dff3a6e98902984ff1df2bbaffe88b Mon Sep 17 00:00:00 2001
+From: John Dennis <jdennis at redhat.com>
+Date: Thu, 15 Nov 2012 14:57:52 -0500
+Subject: [PATCH 113/113] 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:
+
+* 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).
+
+* ipadiscovery no longer retrieves CA cert via HTTP.
+
+* Handle LDAP minssf failures during discovery, treat failure to check
+  ldap server as a warninbg in absebce of a provided CA certificate via
+  --ca-cert-file or though existing /etc/ipa/ca.crt file.
+
+Signed-off-by: Simo Sorce <simo at redhat.com>
+Signed-off-by: Rob Crittenden <rcritten at redhat.com>
+---
+ ipa-client/ipa-install/ipa-client-install | 400 ++++++++++++++++++++++++++++--
+ ipa-client/ipaclient/ipadiscovery.py      |  47 ++--
+ ipa-client/man/ipa-client-install.1       |   8 +
+ ipalib/errors.py                          |  16 ++
+ ipapython/ipautil.py                      |  36 +++
+ ipaserver/install/certs.py                |   5 +-
+ 6 files changed, 460 insertions(+), 52 deletions(-)
+
+--- a/ipa-client/ipa-install/ipa-client-install
++++ b/ipa-client/ipa-install/ipa-client-install
+@@ -25,13 +25,17 @@ try:
+     import os
+     import time
+     import socket
++    import ldap
++    import ldap.sasl
++    import urlparse
+     import logging
+     import tempfile
+     import getpass
+     from ipaclient import ipadiscovery
++    from ipaclient.ipadiscovery import CACERT
+     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
+@@ -39,9 +43,10 @@ try:
+     from ipapython import version
+     from ipapython import certmonger
+     from ipapython.config import IPAOptionParser
++    from ipalib import x509
+     import SSSDConfig
+     from ConfigParser import RawConfigParser
+-    from optparse import SUPPRESS_HELP, OptionGroup
++    from optparse import SUPPRESS_HELP, OptionGroup, OptionValueError
+ except ImportError:
+     print >> sys.stderr, """\
+ There was a problem importing one of the required Python modules. The
+@@ -51,6 +56,7 @@ error was:
+ """ % sys.exc_value
+     sys.exit(1)
+ 
++SUCCESS = 0
+ CLIENT_INSTALL_ERROR = 1
+ CLIENT_NOT_CONFIGURED = 2
+ CLIENT_ALREADY_CONFIGURED = 3
+@@ -59,6 +65,21 @@ CLIENT_UNINSTALL_ERROR = 4 # error after
+ 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 = x509.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")
+@@ -89,6 +110,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",
+@@ -164,6 +188,34 @@ def nickname_exists(nickname):
+         else:
+             return False
+ 
++def cert_summary(msg, cert, indent='    '):
++    if msg:
++        s = '%s\n' % msg
++    else:
++        s = ''
++    s += '%sSubject:     %s\n' % (indent, cert.subject)
++    s += '%sIssuer:      %s\n' % (indent, cert.issuer)
++    s += '%sValid From:  %s\n' % (indent, cert.valid_not_before_str)
++    s += '%sValid Until: %s\n' % (indent, cert.valid_not_after_str)
++
++    return s
++
++def get_cert_path(cert_path):
++    """
++    If a CA certificate is passed in on the command line, use that.
++
++    Else if a CA file exists in CACERT then use that.
++
++    Otherwise return None.
++    """
++    if cert_path is not None:
++        return cert_path
++
++    if os.path.exists(CACERT):
++        return CACERT
++
++    return None
++
+ def emit_quiet(quiet, message):
+     if not quiet:
+         print message
+@@ -583,7 +635,7 @@ def configure_krb5_conf(fstore, cli_base
+                  {'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})
+@@ -733,7 +785,7 @@ def configure_sssd_conf(fstore, cli_real
+     # 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)
+@@ -852,6 +904,309 @@ def client_dns(server, hostname, dns_upd
+     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:
++        parsed = urlparse.urlparse(url, 'file')
++    except Exception, e:
++        raise errors.FileError("unable to parse file url '%s'" % (url))
++
++    if parsed.scheme != 'file':
++        raise errors.FileError("url is not a file scheme '%s'" % (url))
++
++    filename = parsed.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))
++
++    root_logger.debug("trying to retrieve CA cert from file %s", filename)
++    try:
++        cert = x509.load_certificate_from_file(filename)
++    except Exception, e:
++        raise errors.NoCertificateError(entry=filename)
++
++    try:
++        x509.write_certificate(cert.der_data, 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:
++        root_logger.warning("Downloading the CA certificate via HTTP, " +
++                            "this is INSECURE")
++
++    root_logger.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')
++
++    root_logger.debug("trying to retrieve CA cert via LDAP from %s", url)
++
++    conn = ldap.initialize(url)
++    conn.set_option(ldap.OPT_X_SASL_NOCANON, ldap.OPT_ON)
++    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:
++        root_logger.debug("get_ca_cert_from_ldap() error: %s",
++                          convert_ldap_error(e))
++        raise errors.NoCertificateError(entry=url)
++
++    except ldap.SERVER_DOWN, e:
++        root_logger.debug("get_ca_cert_from_ldap() error: %s",
++                          convert_ldap_error(e))
++        raise errors.NetworkError(uri=url, error=str(e))
++    except Exception, e:
++        root_logger.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:
++        x509.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 = x509.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:
++        root_logger.info(
++            cert_summary("Successfully retrieved CA cert", new_ca_cert))
++        return
++
++    if existing_ca_cert.der_data != new_ca_cert.der_data:
++        root_logger.warning(
++            "The CA cert available from the IPA server does not match the\n"
++            "local certificate available at %s" % CACERT)
++        root_logger.warning(
++            cert_summary("Existing CA cert:", existing_ca_cert))
++        root_logger.warning(
++            cert_summary("Retrieved CA cert:", new_ca_cert))
++        if override:
++            root_logger.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:
++        root_logger.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:
++            root_logger.debug(e)
++            raise errors.NoCertificateError(entry=url)
++        root_logger.debug("CA cert provided by user, use it!")
++    else:
++        if os.path.exists(CACERT):
++            if os.path.isfile(CACERT):
++                try:
++                    existing_ca_cert = x509.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:
++                root_logger.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:
++                    root_logger.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:
++                root_logger.debug(str(e))
++                url = http_url()
++                if existing_ca_cert:
++                    root_logger.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:
++                    root_logger.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:
++                        root_logger.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:
++                root_logger.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
+ 
+@@ -876,7 +1231,7 @@ def install(options, env, fstore, states
+     # Create the discovery instance
+     ds = ipadiscovery.IPADiscovery()
+ 
+-    ret = ds.search(domain=options.domain, server=options.server, hostname=hostname)
++    ret = ds.search(domain=options.domain, server=options.server, hostname=hostname, ca_cert_path=get_cert_path(options.ca_cert_file))
+ 
+     if ret == ipadiscovery.BAD_HOST_CONFIG:
+         print >>sys.stderr, "Can't get the fully qualified name of this host"
+@@ -897,7 +1252,7 @@ def install(options, env, fstore, states
+             print "DNS discovery failed to determine your DNS domain"
+             cli_domain = user_input("Provide the domain name of your IPA server (ex: example.com)", allow_empty = False)
+             logging.debug("will use domain: %s\n", cli_domain)
+-        ret = ds.search(domain=cli_domain, server=options.server, hostname=hostname)
++        ret = ds.search(domain=cli_domain, server=options.server, hostname=hostname, ca_cert_path=get_cert_path(options.ca_cert_file))
+ 
+     if not cli_domain:
+         if ds.getDomainName():
+@@ -918,7 +1273,7 @@ def install(options, env, fstore, states
+             print "DNS discovery failed to find the IPA Server"
+             cli_server = user_input("Provide your IPA server name (ex: ipa.example.com)", allow_empty = False)
+             logging.debug("will use server: %s\n", cli_server)
+-        ret = ds.search(domain=cli_domain, server=cli_server, hostname=hostname)
++        ret = ds.search(domain=cli_domain, server=cli_server, hostname=hostname, ca_cert_path=get_cert_path(options.ca_cert_file))
+     else:
+         dnsok = True
+     if not cli_server:
+@@ -936,6 +1291,11 @@ def install(options, env, fstore, states
+         print "Note: This is not an error if anonymous access has been explicitly restricted."
+         ret = 0
+ 
++    if ret == ipadiscovery.NO_TLS_LDAP:
++        print "Warning: The LDAP server requires TLS is but we do not have the CA."
++        print "Proceeding without strict verification."
++        ret = 0
++
+     if ret != 0:
+         print >>sys.stderr, "Failed to verify that "+cli_server+" is an IPA Server."
+         print >>sys.stderr, "This may mean that the remote server is not up or is not reachable"
+@@ -991,20 +1351,6 @@ def install(options, env, fstore, states
+             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
+@@ -1085,6 +1431,15 @@ def install(options, env, fstore, states
+                 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:
++                root_logger.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)
+ 
+@@ -1121,7 +1476,7 @@ def install(options, env, fstore, states
+         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:
+--- a/ipa-client/ipaclient/ipadiscovery.py
++++ b/ipa-client/ipaclient/ipadiscovery.py
+@@ -27,12 +27,14 @@ from ldap import LDAPError
+ from ipapython.ipautil import run, CalledProcessError, valid_ip, get_ipa_basedn, \
+                               realm_to_suffix, format_netloc, parse_items
+ 
++CACERT = '/etc/ipa/ca.crt'
+ 
+ NOT_FQDN = -1
+ NO_LDAP_SERVER = -2
+ REALM_NOT_FOUND = -3
+ NOT_IPA_SERVER = -4
+ NO_ACCESS_TO_LDAP = -5
++NO_TLS_LDAP = -6
+ BAD_HOST_CONFIG = -10
+ UNKNOWN_ERROR = -15
+ 
+@@ -105,7 +107,7 @@ class IPADiscovery:
+                 domain = domain[p+1:]
+         return (None, None)
+ 
+-    def search(self, domain = "", server = "", hostname=None):
++    def search(self, domain = "", server = "", hostname=None, ca_cert_path=None):
+         qname = ""
+         results = []
+         result = []
+@@ -178,14 +180,14 @@ class IPADiscovery:
+         ldapaccess = True
+         for server in servers:
+             # check ldap now
+-            ldapret = self.ipacheckldap(server, self.realm)
++            ldapret = self.ipacheckldap(server, self.realm, ca_cert_path=ca_cert_path)
+ 
+             if ldapret[0] == 0:
+                 self.server = ldapret[1]
+                 self.realm = ldapret[2]
+                 break
+ 
+-            if ldapret[0] == NO_ACCESS_TO_LDAP:
++            if ldapret[0] == NO_ACCESS_TO_LDAP or ldapret[0] == NO_TLS_LDAP:
+                 ldapaccess = False
+ 
+         # If one of LDAP servers checked rejects access (may be anonymous
+@@ -206,12 +208,10 @@ class IPADiscovery:
+ 
+         return ldapret[0]
+ 
+-    def ipacheckldap(self, thost, trealm):
++    def ipacheckldap(self, thost, trealm, ca_cert_path=None):
+         """
+         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:
+@@ -229,29 +229,16 @@ 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:
+             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)
++            if ca_cert_path:
++                ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, True)
++                ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, ca_cert_path)
++                lh.set_option(ldap.OPT_X_TLS_DEMAND, True)
++                lh.start_tls_s()
+             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
+@@ -302,14 +289,16 @@ class IPADiscovery:
+                 logging.debug("LDAP Error: Anonymous acces not allowed")
+                 return [NO_ACCESS_TO_LDAP]
+ 
++            # We should only get UNWILLING_TO_PERFORM if the remote LDAP server
++            # has minssf > 0 and we have attempted a non-TLS connection.
++            if ca_cert_path is None and isinstance(err, ldap.UNWILLING_TO_PERFORM):
++                logging.debug("LDAP server returned UNWILLING_TO_PERFORM. This likely means that minssf is enabled")
++                return [NO_TLS_LDAP]
++
+             logging.error("LDAP Error: %s: %s" %
+                (err.args[0]['desc'], err.args[0].get('info', '')))
+             return [UNKNOWN_ERROR]
+ 
+-        finally:
+-            os.remove("%s/ca.crt" % temp_ca_dir)
+-            os.rmdir(temp_ca_dir)
+-
+ 
+     def ipadnssearchldap(self, tdomain):
+         servers = ""
+--- 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
+--- a/ipalib/errors.py
++++ b/ipalib/errors.py
+@@ -1540,6 +1540,22 @@ class GenericError(PublicError):
+     errno = 5000
+ 
+ 
++class CertificateInvalidError(CertificateError):
++    """
++    **4310** Raised when a certificate is not valid



More information about the Pkg-freeipa-devel mailing list