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@redhat.com>
Signed-off-by: Rob Crittenden <rcritten@redhat.com>
This commit is contained in:
John Dennis
2012-11-15 14:57:52 -05:00
committed by Rob Crittenden
parent 91f4af7e6a
commit a1991aeac1
6 changed files with 465 additions and 56 deletions

View File

@@ -30,11 +30,14 @@ from ipapython.ipautil import run, CalledProcessError, valid_ip, get_ipa_basedn,
realm_to_suffix, format_netloc
from ipapython.dn import DN
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
@@ -45,6 +48,7 @@ error_names = {
REALM_NOT_FOUND: 'REALM_NOT_FOUND',
NOT_IPA_SERVER: 'NOT_IPA_SERVER',
NO_ACCESS_TO_LDAP: 'NO_ACCESS_TO_LDAP',
NO_TLS_LDAP: 'NO_TLS_LDAP',
BAD_HOST_CONFIG: 'BAD_HOST_CONFIG',
UNKNOWN_ERROR: 'UNKNOWN_ERROR',
}
@@ -135,7 +139,7 @@ class IPADiscovery(object):
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):
root_logger.debug("[IPA Discovery]")
root_logger.debug(
'Starting IPA discovery with domain=%s, server=%s, hostname=%s',
@@ -224,14 +228,14 @@ class IPADiscovery(object):
ldapaccess = True
if self.server:
# check ldap now
ldapret = self.ipacheckldap(self.server, self.realm)
ldapret = self.ipacheckldap(self.server, self.realm, ca_cert_path=ca_cert_path)
if ldapret[0] == 0:
self.server = ldapret[1]
self.realm = ldapret[2]
self.server_source = self.realm_source = (
'Discovered from LDAP DNS records in %s' % self.server)
elif ldapret[0] == NO_ACCESS_TO_LDAP:
elif ldapret[0] == NO_ACCESS_TO_LDAP or ldapret[0] == NO_TLS_LDAP:
ldapaccess = False
# If one of LDAP servers checked rejects access (maybe anonymous
@@ -260,12 +264,10 @@ class IPADiscovery(object):
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:
@@ -279,31 +281,17 @@ class IPADiscovery(object):
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:
root_logger.error('Retrieving CA from %s failed', thost)
root_logger.debug('Retrieving CA from %s failed: %s', thost, str(e))
return [NOT_IPA_SERVER]
#now verify the server is really an IPA server
try:
ldap_url = "ldap://" + format_netloc(thost, 389)
root_logger.debug("Init LDAP connection with: %s", ldap_url)
lh = ldap.initialize(ldap_url)
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
@@ -358,14 +346,16 @@ class IPADiscovery(object):
root_logger.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):
root_logger.debug("LDAP server returned UNWILLING_TO_PERFORM. This likely means that minssf is enabled")
return [NO_TLS_LDAP]
root_logger.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 ipadns_search_srv(self, domain, srv_record_name, default_port,
break_on_first=True):