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
This commit is contained in:
Rob Crittenden
2010-07-22 14:16:22 -04:00
parent 830910d1f3
commit d885339f1c
12 changed files with 99 additions and 33 deletions

View File

@@ -1054,6 +1054,22 @@ class DefaultGroupError(ExecutionError):
errno = 4018 errno = 4018
format = _('The default users group cannot be removed') 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): class BuiltinError(ExecutionError):
""" """
**4100** Base class for builtin execution errors (*4100 - 4199*). **4100** Base class for builtin execution errors (*4100 - 4199*).

View File

@@ -269,7 +269,7 @@ class cert_request(VirtualCommand):
if not add: if not add:
raise errors.NotFound(reason="The service principal for this request doesn't exist.") raise errors.NotFound(reason="The service principal for this request doesn't exist.")
try: try:
service = api.Command['service_add'](principal, **{})['result'] service = api.Command['service_add'](principal, **{'force': True})['result']
dn = service['dn'] dn = service['dn']
except errors.ACIError: except errors.ACIError:
raise errors.ACIError(info='You need to be a member of the serviceadmin role to add services') raise errors.ACIError(info='You need to be a member of the serviceadmin role to add services')

View File

@@ -84,7 +84,6 @@ def validate_host(ugettext, fqdn):
return _('Fully-qualified hostname required') return _('Fully-qualified hostname required')
return None return None
class host(LDAPObject): class host(LDAPObject):
""" """
Host object. Host object.
@@ -196,8 +195,15 @@ class host_add(LDAPCreate):
""" """
msg_summary = _('Added host "%(value)s"') 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): 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: if 'locality' in entry_attrs:
entry_attrs['l'] = entry_attrs['locality'] entry_attrs['l'] = entry_attrs['locality']
del entry_attrs['locality'] del entry_attrs['locality']

View File

@@ -60,7 +60,7 @@ EXAMPLES:
""" """
import base64 import base64
from ipalib import api, errors from ipalib import api, errors, util
from ipalib import Str, Flag, Bytes from ipalib import Str, Flag, Bytes
from ipalib.plugins.baseldap import * from ipalib.plugins.baseldap import *
from ipalib import x509 from ipalib import x509
@@ -183,19 +183,11 @@ class service_add(LDAPCreate):
entry_attrs['usercertificate'] = base64.b64decode(cert) entry_attrs['usercertificate'] = base64.b64decode(cert)
# FIXME: shouldn't we request signing at this point? # FIXME: shouldn't we request signing at this point?
# TODO: once DNS client is done (code below for reference only!) if not options.get('force', False):
# if not kw['force']: # We know the host exists if we've gotten this far but we
# fqdn = hostname + '.' # really want to discourage creating services for hosts that
# rs = dnsclient.query(fqdn, dnsclient.DNS_C_IN, dnsclient.DNS_T_A) # don't exist in DNS.
# if len(rs) == 0: util.validate_host_dns(self.log, hostname)
# 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)
# )
return dn return dn

View File

@@ -28,6 +28,7 @@ import time
import krbV import krbV
import socket import socket
from ipalib import errors from ipalib import errors
from ipapython import dnsclient
def get_current_principal(): def get_current_principal():
@@ -113,3 +114,18 @@ def realm_to_suffix(realm_name):
s = realm_name.split(".") s = realm_name.split(".")
terms = ["dc=" + x.lower() for x in s] terms = ["dc=" + x.lower() for x in s]
return ",".join(terms) 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)
)

View File

@@ -70,7 +70,7 @@ class test_ipagetkeytab(cmdline_test):
""" """
# Create the service # Create the service
try: try:
api.Command['host_add'](self.host_fqdn) api.Command['host_add'](self.host_fqdn, force=True)
except errors.DuplicateEntry: except errors.DuplicateEntry:
# it already exists, no problem # it already exists, no problem
pass pass
@@ -93,7 +93,7 @@ class test_ipagetkeytab(cmdline_test):
""" """
# Create the service # Create the service
try: try:
api.Command['service_add'](self.service_princ) api.Command['service_add'](self.service_princ, force=True)
except errors.DuplicateEntry: except errors.DuplicateEntry:
# it already exists, no problem # it already exists, no problem
pass pass

View File

@@ -101,7 +101,7 @@ class test_cert(XMLRPC_test):
This should fail because the service principal doesn't exist This should fail because the service principal doesn't exist
""" """
# First create the host that will use this policy # 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)) csr = unicode(self.generateCSR(self.subject))
try: try:

View File

@@ -149,19 +149,19 @@ class test_hbac(XMLRPC_test):
self.test_group, description=u'description' self.test_group, description=u'description'
) )
self.failsafe_add(api.Object.host, self.failsafe_add(api.Object.host,
self.test_host self.test_host, force=True
) )
self.failsafe_add(api.Object.hostgroup, self.failsafe_add(api.Object.hostgroup,
self.test_hostgroup, description=u'description' self.test_hostgroup, description=u'description'
) )
self.failsafe_add(api.Object.host, self.failsafe_add(api.Object.host,
self.test_sourcehost self.test_sourcehost, force=True
) )
self.failsafe_add(api.Object.hostgroup, self.failsafe_add(api.Object.hostgroup,
self.test_sourcehostgroup, description=u'desc' self.test_sourcehostgroup, description=u'desc'
) )
self.failsafe_add(api.Object.hbacsvc, 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): def test_8_hbac_add_user(self):

View File

@@ -32,12 +32,15 @@ short1 = u'testhost1'
dn1 = u'fqdn=%s,cn=computers,cn=accounts,%s' % (fqdn1, api.env.basedn) dn1 = u'fqdn=%s,cn=computers,cn=accounts,%s' % (fqdn1, api.env.basedn)
service1 = u'dns/%s@%s' % (fqdn1, api.env.realm) service1 = u'dns/%s@%s' % (fqdn1, api.env.realm)
service1dn = u'krbprincipalname=%s,cn=services,cn=accounts,%s' % (service1.lower(), api.env.basedn) 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): class test_host(Declarative):
cleanup_commands = [ cleanup_commands = [
('host_del', [fqdn1], {}), ('host_del', [fqdn1], {}),
('host_del', [fqdn2], {}),
('service_del', [service1], {}), ('service_del', [service1], {}),
] ]
@@ -70,6 +73,7 @@ class test_host(Declarative):
dict( dict(
description=u'Test host 1', description=u'Test host 1',
l=u'Undisclosed location 1', l=u'Undisclosed location 1',
force=True,
), ),
), ),
expected=dict( expected=dict(
@@ -94,6 +98,7 @@ class test_host(Declarative):
dict( dict(
description=u'Test host 1', description=u'Test host 1',
localityname=u'Undisclosed location 1', localityname=u'Undisclosed location 1',
force=True,
), ),
), ),
expected=errors.DuplicateEntry(), expected=errors.DuplicateEntry(),
@@ -267,6 +272,7 @@ class test_host(Declarative):
dict( dict(
description=u'Test host 1', description=u'Test host 1',
l=u'Undisclosed location 1', l=u'Undisclosed location 1',
force=True,
), ),
), ),
expected=dict( expected=dict(
@@ -286,7 +292,7 @@ class test_host(Declarative):
dict( dict(
desc='Add a service to host %r' % fqdn1, desc='Add a service to host %r' % fqdn1,
command=('service_add', [service1], {}), command=('service_add', [service1], {'force': True}),
expected=dict( expected=dict(
value=service1, value=service1,
summary=u'Added service "%s"' % 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],
),
),
),
] ]

View File

@@ -99,6 +99,7 @@ class test_hostgroup(Declarative):
dict( dict(
description=u'Test host 1', description=u'Test host 1',
l=u'Undisclosed location 1', l=u'Undisclosed location 1',
force=True,
), ),
), ),
expected=dict( expected=dict(

View File

@@ -46,7 +46,7 @@ class test_netgroup(XMLRPC_test):
host_fqdn = u'ipatesthost.%s' % api.env.domain host_fqdn = u'ipatesthost.%s' % api.env.domain
host_description = u'Test host' host_description = u'Test host'
host_localityname = u'Undisclosed location' 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_cn = u'hg1'
hg_description = u'Netgroup' hg_description = u'Netgroup'

View File

@@ -40,8 +40,8 @@ class test_service(XMLRPC_test):
""" """
Test adding a HTTP principal using the `xmlrpc.service_add` method. Test adding a HTTP principal using the `xmlrpc.service_add` method.
""" """
self.failsafe_add(api.Object.host, self.host) self.failsafe_add(api.Object.host, self.host, force=True)
entry = self.failsafe_add(api.Object.service, self.principal)['result'] entry = self.failsafe_add(api.Object.service, self.principal, force=True)['result']
assert_attr_equal(entry, 'krbprincipalname', self.principal) assert_attr_equal(entry, 'krbprincipalname', self.principal)
assert_attr_equal(entry, 'objectclass', 'ipaobject') 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 Test adding a host principal using `xmlrpc.service_add`. Host
services are not allowed. 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} kw = {'krbprincipalname': self.hostprincipal}
try: try:
api.Command['service_add'](**kw) api.Command['service_add'](**kw)
@@ -67,7 +62,7 @@ class test_service(XMLRPC_test):
""" """
Test adding a malformed principal ('foo'). Test adding a malformed principal ('foo').
""" """
kw = {'krbprincipalname': u'foo'} kw = {'krbprincipalname': u'foo', 'force': True}
try: try:
api.Command['service_add'](**kw) api.Command['service_add'](**kw)
except errors.MalformedServicePrincipal: except errors.MalformedServicePrincipal:
@@ -79,7 +74,7 @@ class test_service(XMLRPC_test):
""" """
Test adding a malformed principal ('HTTP/foo@FOO.NET'). 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: try:
api.Command['service_add'](**kw) api.Command['service_add'](**kw)
except errors.RealmMismatch: except errors.RealmMismatch:
@@ -115,3 +110,5 @@ class test_service(XMLRPC_test):
pass pass
else: else:
assert False assert False
api.Command['host_del'](self.host)