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

@@ -25,14 +25,18 @@ try:
import os
import time
import socket
import ldap
import ldap.sasl
import urlparse
from ipapython.ipa_log_manager import *
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 sysrestore
@@ -40,12 +44,13 @@ try:
from ipapython import certmonger
from ipapython.config import IPAOptionParser
from ipalib import api, errors
from ipalib import x509
from ipapython.dn import DN
from ipapython.ssh import SSHPublicKey
from ipalib.rpc import delete_persistent_client_session_data
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
@@ -55,6 +60,7 @@ error was:
""" % sys.exc_value
sys.exit(1)
SUCCESS = 0
CLIENT_INSTALL_ERROR = 1
CLIENT_NOT_CONFIGURED = 2
CLIENT_ALREADY_CONFIGURED = 3
@@ -63,6 +69,21 @@ CLIENT_UNINSTALL_ERROR = 4 # error after restoring files/state
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")
@@ -108,6 +129,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",
@@ -171,6 +195,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
# Checks whether nss_ldap or nss-pam-ldapd is installed. If anyone of mandatory files was found returns True and list of all files found.
def nssldap_exists():
files_to_check = [{'function':'configure_ldap_conf', 'mandatory':['/etc/ldap.conf','/etc/nss_ldap.conf','/etc/libnss-ldap.conf'], 'optional':['/etc/pam_ldap.conf']},
@@ -709,7 +761,7 @@ def configure_openldap_conf(fstore, cli_basedn, cli_server):
{'name':'empty', 'type':'empty'},
{'name':'URI', 'type':'option', 'value':'ldaps://'+ cli_server[0]},
{'name':'BASE', 'type':'option', 'value':cli_basedn},
{'name':'TLS_CACERT', 'type':'option', 'value':'/etc/ipa/ca.crt'},
{'name':'TLS_CACERT', 'type':'option', 'value':CACERT},
{'name':'empty', 'type':'empty'}]
target_fname = '/etc/openldap/ldap.conf'
@@ -779,7 +831,7 @@ def configure_krb5_conf(cli_realm, cli_domain, cli_server, cli_kdc, dnsok,
kropts.append({'name':'master_kdc', 'type':'option', 'value':ipautil.format_netloc(server, 88)})
kropts.append({'name':'admin_server', 'type':'option', 'value':ipautil.format_netloc(server, 749)})
kropts.append({'name':'default_domain', 'type':'option', 'value':cli_domain})
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})
@@ -960,7 +1012,7 @@ def configure_sssd_conf(fstore, cli_realm, cli_domain, cli_server, options, clie
# 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)
@@ -1283,6 +1335,309 @@ def print_port_conf_info():
" TCP: 464\n"
" UDP: 464, 123 (if NTP enabled)")
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 = DN(('cn', 'CAcert'), ('cn', 'ipa'), ('cn', 'etc'), 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 to download the CA cert from " + 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
@@ -1340,7 +1695,7 @@ def install(options, env, fstore, statestore):
# Do discovery on the first server passed in, we'll do sanity checking
# on any others
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:
root_logger.error("Can't get the fully qualified name of this host")
@@ -1377,7 +1732,7 @@ def install(options, env, fstore, statestore):
cli_domain_source = 'Provided interactively'
root_logger.debug(
"will use interactively provided domain: %s", 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.domain:
@@ -1401,7 +1756,7 @@ def install(options, env, fstore, statestore):
cli_server = [user_input("Provide your IPA server name (ex: ipa.example.com)", allow_empty = False)]
cli_server_source = 'Provided interactively'
root_logger.debug("will use interactively provided server: %s", cli_server[0])
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:
# Only set dnsok to True if we were not passed in one or more servers
@@ -1439,6 +1794,12 @@ def install(options, env, fstore, statestore):
"has been explicitly restricted.")
ret = 0
if ret == ipadiscovery.NO_TLS_LDAP:
root_logger.warning("The LDAP server requires TLS is but we do not " +
"have the CA.")
root_logger.info("Proceeding without strict verification.")
ret = 0
if ret != 0:
root_logger.error("Failed to verify that %s is an IPA Server.",
cli_server[0])
@@ -1490,7 +1851,7 @@ def install(options, env, fstore, statestore):
# Now do a sanity check on the other servers
if options.server and len(options.server) > 1:
for server in options.server[1:]:
ret = ds.search(domain=cli_domain, server=server, hostname=hostname)
ret = ds.search(domain=cli_domain, server=server, hostname=hostname, ca_cert_path=get_cert_path(options.ca_cert_file))
if ret == ipadiscovery.NOT_IPA_SERVER:
root_logger.error("%s is not an IPA v2 Server.", server)
print_port_conf_info()
@@ -1539,21 +1900,6 @@ def install(options, env, fstore, statestore):
root_logger.debug(
"will use principal provided as option: %s", 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 Exception:
pass
try:
run(["/usr/bin/wget", "-O", "/etc/ipa/ca.crt", "http://%s/ipa/config/ca.crt" % ipautil.format_netloc(cli_server[0])])
except CalledProcessError, e:
root_logger.error(
'Retrieving CA from %s failed: %s', cli_server[0], str(e))
return CLIENT_INSTALL_ERROR
if not options.on_master:
nolog = tuple()
# First test out the kerberos configuration
@@ -1650,6 +1996,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[0], 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)
@@ -1717,7 +2072,7 @@ def install(options, env, fstore, statestore):
# Add the CA to the default NSS database and trust it
try:
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])
except CalledProcessError, e:
root_logger.info("Failed to add CA to the default NSS database.")
return CLIENT_INSTALL_ERROR

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):

View File

@@ -97,6 +97,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

View File

@@ -1682,6 +1682,23 @@ class ProtectedEntryError(ExecutionError):
format = _('%(label)s %(key)s cannot be deleted/modified: %(reason)s')
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

View File

@@ -1174,3 +1174,39 @@ def restore_hostname(statestore):
run(['/bin/hostname', old_hostname])
except CalledProcessError, e:
print >>sys.stderr, "Failed to set this machine hostname back to %s: %s" % (old_hostname, str(e))
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)

View File

@@ -227,7 +227,10 @@ class CertDB(object):
if not subject_base:
self.subject_base = DN(('O', 'IPA'))
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"