From 3d2f8bd72de26c26fbdbeafa94ad7cb29032f2af Mon Sep 17 00:00:00 2001 From: Jr Aquino Date: Tue, 28 Feb 2012 10:29:03 -0800 Subject: [PATCH] 41 During ipa-client-install verify forward and reverse dns lookup of server ipa-server-install has a method for validating forward and reverse via ipaserver/install/installutils.py ipa-client-install does not currently have an equivalent This patch moves verify_fqdn() from installutils.py to ipapython/ipautil.py to validate foward and reverse DNS This patch adds the verify_fqdn() test in ipa-client/ipa-install/ipa-client-install to validate the dns of the FreeIPA server https://fedorahosted.org/freeipa/ticket/2438 --- ipa-client/ipa-install/ipa-client-install | 13 +++- ipapython/ipautil.py | 154 ++++++++++++++++++++++++++++- ipaserver/install/installutils.py | 151 +---------------------------- 3 files changed, 166 insertions(+), 152 deletions(-) diff --git a/ipa-client/ipa-install/ipa-client-install b/ipa-client/ipa-install/ipa-client-install index f5c1efe..d568f7e 100755 --- a/ipa-client/ipa-install/ipa-client-install +++ b/ipa-client/ipa-install/ipa-client-install @@ -32,7 +32,7 @@ try: from ipaclient import ipadiscovery import ipaclient.ipachangeconf import ipaclient.ntpconf - from ipapython.ipautil import run, user_input, CalledProcessError, file_exists, realm_to_suffix + from ipapython.ipautil import run, user_input, CalledProcessError, file_exists, realm_to_suffix, verify_fqdn import ipapython.services as ipaservices from ipapython import ipautil from ipapython import dnsclient @@ -88,6 +88,9 @@ def parse_options(): help="configure OpenSSH client to trust DNS SSHFP records") basic_group.add_option("--no-sshd", dest="conf_sshd", default=True, action="store_false", help="do not configure OpenSSH server") + basic_group.add_option("--no-host-dns", dest="no_host_dns", action="store_true", + default=False, + help="Do not use DNS for hostname lookup during installation") basic_group.add_option("--no-dns-sshfp", dest="create_sshfp", default=True, action="store_false", help="do not automatically create DNS SSHFP records") basic_group.add_option("-f", "--force", dest="force", action="store_true", @@ -1121,6 +1124,14 @@ def install(options, env, fstore, statestore): print >>sys.stderr, "due to network or firewall settings." return CLIENT_INSTALL_ERROR + try: + verify_fqdn(cli_server,options.no_host_dns) + except: + print >>sys.stderr, "Failed to verify DNS for "+cli_server+" from this client." + print >>sys.stderr, "Please check your DNS configuration and try again.\n" + if not options.force: + return CLIENT_INSTALL_ERROR + cli_kdc = ds.getKDCName() if dnsok and not cli_kdc: print >>sys.stderr, "DNS domain '%s' is not configured for automatic KDC address lookup." % ds.getRealmName().lower() diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py index 20f7578..e0ecb3f 100644 --- a/ipapython/ipautil.py +++ b/ipapython/ipautil.py @@ -37,8 +37,9 @@ import urllib2 import socket import ldap import struct +import errno -from ipapython import ipavalidate +from ipapython import ipavalidate, dnsclient from types import * import re @@ -61,6 +62,22 @@ except ImportError: return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode) from ipapython.compat import sha1, md5 +# Used by verify_fqdn() +class BadHostError(Exception): + pass + +class HostLookupError(BadHostError): + pass + +class HostForwardLookupError(HostLookupError): + pass + +class HostReverseLookupError(HostLookupError): + pass + +class HostnameLocalhost(HostLookupError): + pass + def get_domain_name(): try: config.init_config() @@ -164,6 +181,141 @@ class CheckedIPAddress(netaddr.IPAddress): def valid_ip(addr): return netaddr.valid_ipv4(addr) or netaddr.valid_ipv6(addr) +def verify_fqdn(host_name, no_host_dns=False, local_hostname=True): + """ + Run fqdn checks for given host: + - test hostname format + - test that hostname is fully qualified + - test forward and reverse hostname DNS lookup + + Raises `BadHostError` or derived Exceptions if there is an error + + :param host_name: The host name to verify. + :param no_host_dns: If true, skip DNS resolution tests of the host name. + :param local_hostname: If true, run additional checks for local hostnames + """ + if len(host_name.split(".")) < 2 or host_name == "localhost.localdomain": + raise BadHostError("Invalid hostname '%s', must be fully-qualified." % host_name) + + if host_name != host_name.lower(): + raise BadHostError("Invalid hostname '%s', must be lower-case." % host_name) + + if valid_ip(host_name): + raise BadHostError("IP address not allowed as a hostname") + + if local_hostname: + try: + ex_name = socket.gethostbyaddr(host_name) + if host_name != ex_name[0]: + raise HostLookupError("The host name %s does not match the primary host name %s. "\ + "Please check /etc/hosts or DNS name resolution" % (host_name, ex_name[0])) + except socket.gaierror: + pass + + if no_host_dns: + print "Warning: skipping DNS resolution of host", host_name + return + + try: + hostaddr = socket.getaddrinfo(host_name, None) + except: + raise HostForwardLookupError("Unable to resolve host name, check /etc/hosts or DNS name resolution") + + if len(hostaddr) == 0: + raise HostForwardLookupError("Unable to resolve host name, check /etc/hosts or DNS name resolution") + + for a in hostaddr: + if a[4][0] == '127.0.0.1' or a[4][0] == '::1': + raise HostForwardLookupError("The IPA Server hostname must not resolve to localhost (%s). A routable IP address must be used. Check /etc/hosts to see if %s is an alias for %s" % (a[4][0], host_name, a[4][0])) + try: + resaddr = a[4][0] + revname = socket.gethostbyaddr(a[4][0])[0] + except: + raise HostReverseLookupError("Unable to resolve the reverse ip address, check /etc/hosts or DNS name resolution") + if revname != host_name: + raise HostReverseLookupError("The host name %s does not match the reverse lookup %s" % (host_name, revname)) + + # Verify this is NOT a CNAME + rs = dnsclient.query(host_name+".", dnsclient.DNS_C_IN, dnsclient.DNS_T_CNAME) + if len(rs) != 0: + for rsn in rs: + if rsn.dns_type == dnsclient.DNS_T_CNAME: + raise HostReverseLookupError("The IPA Server Hostname cannot be a CNAME, only A and AAAA names are allowed.") + + # Verify that it is a DNS A or AAAA record + rs = dnsclient.query(host_name+".", dnsclient.DNS_C_IN, dnsclient.DNS_T_A) + if len([ rec for rec in rs if rec.dns_type is not dnsclient.DNS_T_SOA ]) > 0: + verify_dns_records(host_name, rs, resaddr, 'ipv4') + return + + rs = dnsclient.query(host_name+".", dnsclient.DNS_C_IN, dnsclient.DNS_T_AAAA) + if len([ rec for rec in rs if rec.dns_type is not dnsclient.DNS_T_SOA ]) > 0: + verify_dns_records(host_name, rs, resaddr, 'ipv6') + return + else: + print "Warning: Hostname (%s) not found in DNS" % host_name + +def verify_dns_records(host_name, responses, resaddr, family): + familykw = { 'ipv4' : { + 'dns_type' : dnsclient.DNS_T_A, + 'socket_family' : socket.AF_INET, + }, + 'ipv6' : { + 'dns_type' : dnsclient.DNS_T_AAAA, + 'socket_family' : socket.AF_INET6, + }, + } + + family = family.lower() + if family not in familykw.keys(): + raise RuntimeError("Unknown faimily %s\n" % family) + + rec_list = [] + for rsn in responses: + if rsn.section == dnsclient.DNS_S_ANSWER and \ + rsn.dns_type == familykw[family]['dns_type']: + rec_list.append(rsn) + + if not rec_list: + raise IOError(errno.ENOENT, + "Warning: Hostname (%s) not found in DNS" % host_name) + + if family == 'ipv4': + familykw[family]['address'] = [socket.inet_ntop(socket.AF_INET, + struct.pack('!L',rec.rdata.address)) \ + for rec in rec_list] + else: + familykw[family]['address'] = [socket.inet_ntop(socket.AF_INET6, + struct.pack('!16B', *rec.rdata.address)) \ + for rec in rec_list] + + # Check that DNS address is the same is address returned via standard glibc calls + dns_addrs = [netaddr.IPAddress(addr) for addr in familykw[family]['address']] + dns_addr = None + for addr in dns_addrs: + if addr.format() == resaddr: + dns_addr = addr + break + + if dns_addr is None: + raise RuntimeError("Host address %s does not match any address in DNS lookup." % resaddr) + + rs = dnsclient.query(dns_addr.reverse_dns, dnsclient.DNS_C_IN, dnsclient.DNS_T_PTR) + if len(rs) == 0: + raise RuntimeError("Cannot find Reverse Address for %s (%s)" % (host_name, dns_addr.format())) + + rev = None + for rsn in rs: + if rsn.dns_type == dnsclient.DNS_T_PTR: + rev = rsn + break + + if rev == None: + raise RuntimeError("Cannot find Reverse Address for %s (%s)" % (host_name, dns_addr.format())) + + if rec.dns_name != rev.rdata.ptrdname: + raise RuntimeError("The DNS forward record %s does not match the reverse address %s" % (rec.dns_name, rev.rdata.ptrdname)) + def format_netloc(host, port=None): """ Format network location (host:port). diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py index a9a3ec4..4b3afe9 100644 --- a/ipaserver/install/installutils.py +++ b/ipaserver/install/installutils.py @@ -34,25 +34,11 @@ from ConfigParser import SafeConfigParser from ipapython import ipautil, dnsclient, sysrestore from ipapython.ipa_log_manager import * +from ipapython.ipautil import * # Used to determine install status IPA_MODULES = ['httpd', 'kadmin', 'dirsrv', 'pki-cad', 'pkids', 'install', 'krb5kdc', 'ntpd', 'named', 'ipa_memcached'] -class BadHostError(Exception): - pass - -class HostLookupError(BadHostError): - pass - -class HostForwardLookupError(HostLookupError): - pass - -class HostReverseLookupError(HostLookupError): - pass - -class HostnameLocalhost(HostLookupError): - pass - class ReplicaConfig: def __init__(self): self.realm_name = "" @@ -75,141 +61,6 @@ def get_fqdn(): fqdn = "" return fqdn -def verify_dns_records(host_name, responses, resaddr, family): - familykw = { 'ipv4' : { - 'dns_type' : dnsclient.DNS_T_A, - 'socket_family' : socket.AF_INET, - }, - 'ipv6' : { - 'dns_type' : dnsclient.DNS_T_AAAA, - 'socket_family' : socket.AF_INET6, - }, - } - - family = family.lower() - if family not in familykw.keys(): - raise RuntimeError("Unknown faimily %s\n" % family) - - rec_list = [] - for rsn in responses: - if rsn.section == dnsclient.DNS_S_ANSWER and \ - rsn.dns_type == familykw[family]['dns_type']: - rec_list.append(rsn) - - if not rec_list: - raise IOError(errno.ENOENT, - "Warning: Hostname (%s) not found in DNS" % host_name) - - if family == 'ipv4': - familykw[family]['address'] = [socket.inet_ntop(socket.AF_INET, - struct.pack('!L',rec.rdata.address)) \ - for rec in rec_list] - else: - familykw[family]['address'] = [socket.inet_ntop(socket.AF_INET6, - struct.pack('!16B', *rec.rdata.address)) \ - for rec in rec_list] - - # Check that DNS address is the same is address returned via standard glibc calls - dns_addrs = [netaddr.IPAddress(addr) for addr in familykw[family]['address']] - dns_addr = None - for addr in dns_addrs: - if addr.format() == resaddr: - dns_addr = addr - break - - if dns_addr is None: - raise RuntimeError("Host address %s does not match any address in DNS lookup." % resaddr) - - rs = dnsclient.query(dns_addr.reverse_dns, dnsclient.DNS_C_IN, dnsclient.DNS_T_PTR) - if len(rs) == 0: - raise RuntimeError("Cannot find Reverse Address for %s (%s)" % (host_name, dns_addr.format())) - - rev = None - for rsn in rs: - if rsn.dns_type == dnsclient.DNS_T_PTR: - rev = rsn - break - - if rev == None: - raise RuntimeError("Cannot find Reverse Address for %s (%s)" % (host_name, dns_addr.format())) - - if rec.dns_name != rev.rdata.ptrdname: - raise RuntimeError("The DNS forward record %s does not match the reverse address %s" % (rec.dns_name, rev.rdata.ptrdname)) - - -def verify_fqdn(host_name, no_host_dns=False, local_hostname=True): - """ - Run fqdn checks for given host: - - test hostname format - - test that hostname is fully qualified - - test forward and reverse hostname DNS lookup - - Raises `BadHostError` or derived Exceptions if there is an error - - :param host_name: The host name to verify. - :param no_host_dns: If true, skip DNS resolution tests of the host name. - :param local_hostname: If true, run additional checks for local hostnames - """ - if len(host_name.split(".")) < 2 or host_name == "localhost.localdomain": - raise BadHostError("Invalid hostname '%s', must be fully-qualified." % host_name) - - if host_name != host_name.lower(): - raise BadHostError("Invalid hostname '%s', must be lower-case." % host_name) - - if ipautil.valid_ip(host_name): - raise BadHostError("IP address not allowed as a hostname") - - if local_hostname: - try: - ex_name = socket.gethostbyaddr(host_name) - if host_name != ex_name[0]: - raise HostLookupError("The host name %s does not match the primary host name %s. "\ - "Please check /etc/hosts or DNS name resolution" % (host_name, ex_name[0])) - except socket.gaierror: - pass - - if no_host_dns: - print "Warning: skipping DNS resolution of host", host_name - return - - try: - hostaddr = socket.getaddrinfo(host_name, None) - except: - raise HostForwardLookupError("Unable to resolve host name, check /etc/hosts or DNS name resolution") - - if len(hostaddr) == 0: - raise HostForwardLookupError("Unable to resolve host name, check /etc/hosts or DNS name resolution") - - for a in hostaddr: - if a[4][0] == '127.0.0.1' or a[4][0] == '::1': - raise HostForwardLookupError("The IPA Server hostname must not resolve to localhost (%s). A routable IP address must be used. Check /etc/hosts to see if %s is an alias for %s" % (a[4][0], host_name, a[4][0])) - try: - resaddr = a[4][0] - revname = socket.gethostbyaddr(a[4][0])[0] - except: - raise HostReverseLookupError("Unable to resolve the reverse ip address, check /etc/hosts or DNS name resolution") - if revname != host_name: - raise HostReverseLookupError("The host name %s does not match the reverse lookup %s" % (host_name, revname)) - - # Verify this is NOT a CNAME - rs = dnsclient.query(host_name+".", dnsclient.DNS_C_IN, dnsclient.DNS_T_CNAME) - if len(rs) != 0: - for rsn in rs: - if rsn.dns_type == dnsclient.DNS_T_CNAME: - raise HostReverseLookupError("The IPA Server Hostname cannot be a CNAME, only A and AAAA names are allowed.") - - # Verify that it is a DNS A or AAAA record - rs = dnsclient.query(host_name+".", dnsclient.DNS_C_IN, dnsclient.DNS_T_A) - if len([ rec for rec in rs if rec.dns_type is not dnsclient.DNS_T_SOA ]) > 0: - verify_dns_records(host_name, rs, resaddr, 'ipv4') - return - - rs = dnsclient.query(host_name+".", dnsclient.DNS_C_IN, dnsclient.DNS_T_AAAA) - if len([ rec for rec in rs if rec.dns_type is not dnsclient.DNS_T_SOA ]) > 0: - verify_dns_records(host_name, rs, resaddr, 'ipv6') - return - else: - print "Warning: Hostname (%s) not found in DNS" % host_name def record_in_hosts(ip, host_name=None, file="/etc/hosts"): """ -- 1.7.6.5