From d885339f1cbf208b06c1eb26c49c60d11d62f1c3 Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Thu, 22 Jul 2010 14:16:22 -0400 Subject: Require that hosts be resolvable in DNS. Use --force to ignore warnings. This also requires a resolvable hostname on services as well. I want people to think long and hard about adding things that aren't resolvable. The cert plugin can automatically create services on the user's behalf when issuing a cert. It will always set the force flag to True. We use a lot of made-up host names in the test system, all of which require the force flag now. ticket #25 --- ipalib/errors.py | 16 ++++++++++++ ipalib/plugins/cert.py | 2 +- ipalib/plugins/host.py | 8 +++++- ipalib/plugins/service.py | 20 +++++---------- ipalib/util.py | 16 ++++++++++++ tests/test_cmdline/test_ipagetkeytab.py | 4 +-- tests/test_xmlrpc/test_cert.py | 2 +- tests/test_xmlrpc/test_hbac_plugin.py | 6 ++--- tests/test_xmlrpc/test_host_plugin.py | 40 +++++++++++++++++++++++++++++- tests/test_xmlrpc/test_hostgroup_plugin.py | 1 + tests/test_xmlrpc/test_netgroup_plugin.py | 2 +- tests/test_xmlrpc/test_service_plugin.py | 15 +++++------ 12 files changed, 99 insertions(+), 33 deletions(-) diff --git a/ipalib/errors.py b/ipalib/errors.py index d1d39a37..c35d424a 100644 --- a/ipalib/errors.py +++ b/ipalib/errors.py @@ -1054,6 +1054,22 @@ class DefaultGroupError(ExecutionError): errno = 4018 format = _('The default users group cannot be removed') +class DNSNotARecordError(ExecutionError): + """ + **4019** Raised when a hostname is not a DNS A record + + For example: + + >>> raise DNSNotARecordError() + Traceback (most recent call last): + ... + DNSNotARecordError: Host does not have corresponding DNS A record + + """ + + errno = 4019 + format = _('Host does not have corresponding DNS A record') + class BuiltinError(ExecutionError): """ **4100** Base class for builtin execution errors (*4100 - 4199*). diff --git a/ipalib/plugins/cert.py b/ipalib/plugins/cert.py index ed1d65ad..8920cfe4 100644 --- a/ipalib/plugins/cert.py +++ b/ipalib/plugins/cert.py @@ -269,7 +269,7 @@ class cert_request(VirtualCommand): if not add: raise errors.NotFound(reason="The service principal for this request doesn't exist.") try: - service = api.Command['service_add'](principal, **{})['result'] + service = api.Command['service_add'](principal, **{'force': True})['result'] dn = service['dn'] except errors.ACIError: raise errors.ACIError(info='You need to be a member of the serviceadmin role to add services') diff --git a/ipalib/plugins/host.py b/ipalib/plugins/host.py index b42cbbcb..78d4d5a0 100644 --- a/ipalib/plugins/host.py +++ b/ipalib/plugins/host.py @@ -84,7 +84,6 @@ def validate_host(ugettext, fqdn): return _('Fully-qualified hostname required') return None - class host(LDAPObject): """ Host object. @@ -196,8 +195,15 @@ class host_add(LDAPCreate): """ msg_summary = _('Added host "%(value)s"') + takes_options = ( + Flag('force', + doc=_('force host name even if not in DNS'), + ), + ) def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + if not options.get('force', False): + util.validate_host_dns(self.log, keys[-1]) if 'locality' in entry_attrs: entry_attrs['l'] = entry_attrs['locality'] del entry_attrs['locality'] diff --git a/ipalib/plugins/service.py b/ipalib/plugins/service.py index 392ae60e..ac949b78 100644 --- a/ipalib/plugins/service.py +++ b/ipalib/plugins/service.py @@ -60,7 +60,7 @@ EXAMPLES: """ import base64 -from ipalib import api, errors +from ipalib import api, errors, util from ipalib import Str, Flag, Bytes from ipalib.plugins.baseldap import * from ipalib import x509 @@ -183,19 +183,11 @@ class service_add(LDAPCreate): entry_attrs['usercertificate'] = base64.b64decode(cert) # FIXME: shouldn't we request signing at this point? - # TODO: once DNS client is done (code below for reference only!) - # if not kw['force']: - # fqdn = hostname + '.' - # rs = dnsclient.query(fqdn, dnsclient.DNS_C_IN, dnsclient.DNS_T_A) - # if len(rs) == 0: - # self.log.debug( - # 'IPA: DNS A record lookup failed for '%s'" % hostname - # ) - # raise ipaerror.gen_exception(ipaerror.INPUT_NOT_DNS_A_RECORD) - # else: - # self.log.debug( - # 'IPA: found %d records for '%s'" % (len(rs), hostname) - # ) + if not options.get('force', False): + # We know the host exists if we've gotten this far but we + # really want to discourage creating services for hosts that + # don't exist in DNS. + util.validate_host_dns(self.log, hostname) return dn diff --git a/ipalib/util.py b/ipalib/util.py index 76be9a6d..570d66e0 100644 --- a/ipalib/util.py +++ b/ipalib/util.py @@ -28,6 +28,7 @@ import time import krbV import socket from ipalib import errors +from ipapython import dnsclient def get_current_principal(): @@ -113,3 +114,18 @@ def realm_to_suffix(realm_name): s = realm_name.split(".") terms = ["dc=" + x.lower() for x in s] return ",".join(terms) + +def validate_host_dns(log, fqdn): + """ + See if the hostname has a DNS A record. + """ + rs = dnsclient.query(fqdn + '.', dnsclient.DNS_C_IN, dnsclient.DNS_T_A) + if len(rs) == 0: + log.debug( + 'IPA: DNS A record lookup failed for %s' % fqdn + ) + raise errors.DNSNotARecordError() + else: + log.debug( + 'IPA: found %d records for %s' % (len(rs), fqdn) + ) diff --git a/tests/test_cmdline/test_ipagetkeytab.py b/tests/test_cmdline/test_ipagetkeytab.py index 5c9d58cd..2c51102e 100644 --- a/tests/test_cmdline/test_ipagetkeytab.py +++ b/tests/test_cmdline/test_ipagetkeytab.py @@ -70,7 +70,7 @@ class test_ipagetkeytab(cmdline_test): """ # Create the service try: - api.Command['host_add'](self.host_fqdn) + api.Command['host_add'](self.host_fqdn, force=True) except errors.DuplicateEntry: # it already exists, no problem pass @@ -93,7 +93,7 @@ class test_ipagetkeytab(cmdline_test): """ # Create the service try: - api.Command['service_add'](self.service_princ) + api.Command['service_add'](self.service_princ, force=True) except errors.DuplicateEntry: # it already exists, no problem pass diff --git a/tests/test_xmlrpc/test_cert.py b/tests/test_xmlrpc/test_cert.py index 5d594891..9ea1744a 100644 --- a/tests/test_xmlrpc/test_cert.py +++ b/tests/test_xmlrpc/test_cert.py @@ -101,7 +101,7 @@ class test_cert(XMLRPC_test): This should fail because the service principal doesn't exist """ # First create the host that will use this policy - res = api.Command['host_add'](self.host_fqdn)['result'] + res = api.Command['host_add'](self.host_fqdn, force= True)['result'] csr = unicode(self.generateCSR(self.subject)) try: diff --git a/tests/test_xmlrpc/test_hbac_plugin.py b/tests/test_xmlrpc/test_hbac_plugin.py index cc6e2f55..d9083741 100644 --- a/tests/test_xmlrpc/test_hbac_plugin.py +++ b/tests/test_xmlrpc/test_hbac_plugin.py @@ -149,19 +149,19 @@ class test_hbac(XMLRPC_test): self.test_group, description=u'description' ) self.failsafe_add(api.Object.host, - self.test_host + self.test_host, force=True ) self.failsafe_add(api.Object.hostgroup, self.test_hostgroup, description=u'description' ) self.failsafe_add(api.Object.host, - self.test_sourcehost + self.test_sourcehost, force=True ) self.failsafe_add(api.Object.hostgroup, self.test_sourcehostgroup, description=u'desc' ) self.failsafe_add(api.Object.hbacsvc, - self.test_service, description=u'desc' + self.test_service, description=u'desc', force=True ) def test_8_hbac_add_user(self): diff --git a/tests/test_xmlrpc/test_host_plugin.py b/tests/test_xmlrpc/test_host_plugin.py index 7ae068c3..8c2224b5 100644 --- a/tests/test_xmlrpc/test_host_plugin.py +++ b/tests/test_xmlrpc/test_host_plugin.py @@ -32,12 +32,15 @@ short1 = u'testhost1' dn1 = u'fqdn=%s,cn=computers,cn=accounts,%s' % (fqdn1, api.env.basedn) service1 = u'dns/%s@%s' % (fqdn1, api.env.realm) service1dn = u'krbprincipalname=%s,cn=services,cn=accounts,%s' % (service1.lower(), api.env.basedn) +fqdn2 = u'shouldnotexist.%s' % api.env.domain +dn2 = u'fqdn=%s,cn=computers,cn=accounts,%s' % (fqdn2, api.env.basedn) class test_host(Declarative): cleanup_commands = [ ('host_del', [fqdn1], {}), + ('host_del', [fqdn2], {}), ('service_del', [service1], {}), ] @@ -70,6 +73,7 @@ class test_host(Declarative): dict( description=u'Test host 1', l=u'Undisclosed location 1', + force=True, ), ), expected=dict( @@ -94,6 +98,7 @@ class test_host(Declarative): dict( description=u'Test host 1', localityname=u'Undisclosed location 1', + force=True, ), ), expected=errors.DuplicateEntry(), @@ -267,6 +272,7 @@ class test_host(Declarative): dict( description=u'Test host 1', l=u'Undisclosed location 1', + force=True, ), ), expected=dict( @@ -286,7 +292,7 @@ class test_host(Declarative): dict( desc='Add a service to host %r' % fqdn1, - command=('service_add', [service1], {}), + command=('service_add', [service1], {'force': True}), expected=dict( value=service1, summary=u'Added service "%s"' % service1, @@ -321,4 +327,36 @@ class test_host(Declarative): ), ), + + dict( + desc='Try to add host not in DNS %r without force' % fqdn2, + command=('host_add', [fqdn2], {}), + expected=errors.DNSNotARecordError(reason='Host does not have corresponding DNS A record'), + ), + + + dict( + desc='Try to add host not in DNS %r with force' % fqdn2, + command=('host_add', [fqdn2], + dict( + description=u'Test host 2', + l=u'Undisclosed location 2', + force=True, + ), + ), + expected=dict( + value=fqdn2, + summary=u'Added host "%s"' % fqdn2, + result=dict( + dn=dn2, + fqdn=[fqdn2], + description=[u'Test host 2'], + l=[u'Undisclosed location 2'], + krbprincipalname=[u'host/%s@%s' % (fqdn2, api.env.realm)], + objectclass=objectclasses.host, + ipauniqueid=[fuzzy_uuid], + ), + ), + ), + ] diff --git a/tests/test_xmlrpc/test_hostgroup_plugin.py b/tests/test_xmlrpc/test_hostgroup_plugin.py index 407d9ba1..70cbe3d4 100644 --- a/tests/test_xmlrpc/test_hostgroup_plugin.py +++ b/tests/test_xmlrpc/test_hostgroup_plugin.py @@ -99,6 +99,7 @@ class test_hostgroup(Declarative): dict( description=u'Test host 1', l=u'Undisclosed location 1', + force=True, ), ), expected=dict( diff --git a/tests/test_xmlrpc/test_netgroup_plugin.py b/tests/test_xmlrpc/test_netgroup_plugin.py index 3363883a..3c1fc34d 100644 --- a/tests/test_xmlrpc/test_netgroup_plugin.py +++ b/tests/test_xmlrpc/test_netgroup_plugin.py @@ -46,7 +46,7 @@ class test_netgroup(XMLRPC_test): host_fqdn = u'ipatesthost.%s' % api.env.domain host_description = u'Test host' host_localityname = u'Undisclosed location' - host_kw = {'fqdn': host_fqdn, 'description': host_description, 'localityname': host_localityname, 'raw': True} + host_kw = {'fqdn': host_fqdn, 'description': host_description, 'localityname': host_localityname, 'raw': True, 'force': True} hg_cn = u'hg1' hg_description = u'Netgroup' diff --git a/tests/test_xmlrpc/test_service_plugin.py b/tests/test_xmlrpc/test_service_plugin.py index 299c64fc..96827e4c 100644 --- a/tests/test_xmlrpc/test_service_plugin.py +++ b/tests/test_xmlrpc/test_service_plugin.py @@ -40,8 +40,8 @@ class test_service(XMLRPC_test): """ Test adding a HTTP principal using the `xmlrpc.service_add` method. """ - self.failsafe_add(api.Object.host, self.host) - entry = self.failsafe_add(api.Object.service, self.principal)['result'] + self.failsafe_add(api.Object.host, self.host, force=True) + entry = self.failsafe_add(api.Object.service, self.principal, force=True)['result'] assert_attr_equal(entry, 'krbprincipalname', self.principal) assert_attr_equal(entry, 'objectclass', 'ipaobject') @@ -50,11 +50,6 @@ class test_service(XMLRPC_test): Test adding a host principal using `xmlrpc.service_add`. Host services are not allowed. """ - # FIXME: Are host principals not allowed still? Running this test gives - # this error: - # - # NotFound: The host 'ipatest.example.com' does not exist to add a service to. - kw = {'krbprincipalname': self.hostprincipal} try: api.Command['service_add'](**kw) @@ -67,7 +62,7 @@ class test_service(XMLRPC_test): """ Test adding a malformed principal ('foo'). """ - kw = {'krbprincipalname': u'foo'} + kw = {'krbprincipalname': u'foo', 'force': True} try: api.Command['service_add'](**kw) except errors.MalformedServicePrincipal: @@ -79,7 +74,7 @@ class test_service(XMLRPC_test): """ Test adding a malformed principal ('HTTP/foo@FOO.NET'). """ - kw = {'krbprincipalname': u'HTTP/foo@FOO.NET'} + kw = {'krbprincipalname': u'HTTP/foo@FOO.NET', 'force': True} try: api.Command['service_add'](**kw) except errors.RealmMismatch: @@ -115,3 +110,5 @@ class test_service(XMLRPC_test): pass else: assert False + + api.Command['host_del'](self.host) -- cgit