[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