mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
Enforce that all NS records are resolvable
Bind cannot load a zone if any of its name server records is not resolvable. https://fedorahosted.org/freeipa/ticket/838
This commit is contained in:
2
API.txt
2
API.txt
@@ -717,6 +717,8 @@ option: Str('idnsupdatepolicy', attribute=True, cli_name='update_policy', label=
|
|||||||
option: Flag('idnsallowdynupdate', attribute=True, autofill=True, cli_name='allow_dynupdate', default=False, label=Gettext('Dynamic update', domain='ipa', localedir=None), multivalue=False, required=True)
|
option: Flag('idnsallowdynupdate', attribute=True, autofill=True, cli_name='allow_dynupdate', default=False, label=Gettext('Dynamic update', domain='ipa', localedir=None), multivalue=False, required=True)
|
||||||
option: Str('addattr*', validate_add_attribute, cli_name='addattr', exclude='webui')
|
option: Str('addattr*', validate_add_attribute, cli_name='addattr', exclude='webui')
|
||||||
option: Str('setattr*', validate_set_attribute, cli_name='setattr', exclude='webui')
|
option: Str('setattr*', validate_set_attribute, cli_name='setattr', exclude='webui')
|
||||||
|
option: Flag('force', autofill=True, default=False,lag('force', autofill=True, default=False, doc=Gettext('force DNS zone even if name server not in DNS', domain='ipa', localedir=None))
|
||||||
|
option: Str('ip_address?', _validate_ipaddr,tr('ip_address?', _validate_ipaddr, doc=Gettext('Add the nameserver to DNS with this IP address', domain='ipa', localedir=None))
|
||||||
option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui', flags=['no_output'])
|
option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui', flags=['no_output'])
|
||||||
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui', flags=['no_output'])
|
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui', flags=['no_output'])
|
||||||
option: Str('version?', exclude='webui', flags=['no_option', 'no_output'])
|
option: Str('version?', exclude='webui', flags=['no_option', 'no_output'])
|
||||||
|
|||||||
@@ -151,6 +151,24 @@ def has_cli_options(entry, no_option_msg):
|
|||||||
raise errors.OptionError(no_option_msg)
|
raise errors.OptionError(no_option_msg)
|
||||||
return entry
|
return entry
|
||||||
|
|
||||||
|
def is_ns_rec_resolvable(name):
|
||||||
|
try:
|
||||||
|
return api.Command['dns_resolve'](name)
|
||||||
|
except errors.NotFound:
|
||||||
|
raise errors.NotFound(reason=_('Nameserver \'%(host)s\' does not have a corresponding A/AAAA record' % {'host':name}))
|
||||||
|
|
||||||
|
def add_forward_record(zone, name, str_address):
|
||||||
|
addr = netaddr.IPAddress(str_address)
|
||||||
|
try:
|
||||||
|
if addr.version == 4:
|
||||||
|
api.Command['dnsrecord_add'](zone, name, arecord=str_address)
|
||||||
|
elif addr.version == 6:
|
||||||
|
api.Command['dnsrecord_add'](zone, name, aaaarecord=str_address)
|
||||||
|
else:
|
||||||
|
raise ValueError('Invalid address family')
|
||||||
|
except errors.EmptyModlist:
|
||||||
|
pass # the entry already exists and matches
|
||||||
|
|
||||||
def dns_container_exists(ldap):
|
def dns_container_exists(ldap):
|
||||||
try:
|
try:
|
||||||
ldap.get_entry(api.env.container_dns, [])
|
ldap.get_entry(api.env.container_dns, [])
|
||||||
@@ -266,6 +284,15 @@ class dnszone_add(LDAPCreate):
|
|||||||
"""
|
"""
|
||||||
Create new DNS zone (SOA record).
|
Create new DNS zone (SOA record).
|
||||||
"""
|
"""
|
||||||
|
takes_options = LDAPCreate.takes_options + (
|
||||||
|
Flag('force',
|
||||||
|
doc=_('force DNS zone even if name server not in DNS'),
|
||||||
|
),
|
||||||
|
Str('ip_address?', _validate_ipaddr,
|
||||||
|
doc=_('Add the nameserver to DNS with this IP address'),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
|
def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
|
||||||
if not dns_container_exists(self.api.Backend.ldap2):
|
if not dns_container_exists(self.api.Backend.ldap2):
|
||||||
raise errors.NotFound(reason=_('DNS is not configured'))
|
raise errors.NotFound(reason=_('DNS is not configured'))
|
||||||
@@ -275,13 +302,29 @@ class dnszone_add(LDAPCreate):
|
|||||||
entry_attrs.get('idnsallowdynupdate', False)
|
entry_attrs.get('idnsallowdynupdate', False)
|
||||||
).upper()
|
).upper()
|
||||||
|
|
||||||
|
# Check nameserver has a forward record
|
||||||
nameserver = entry_attrs['idnssoamname']
|
nameserver = entry_attrs['idnssoamname']
|
||||||
|
|
||||||
|
if not 'ip_address' in options and not options['force']:
|
||||||
|
is_ns_rec_resolvable(nameserver)
|
||||||
|
|
||||||
if nameserver[-1] != '.':
|
if nameserver[-1] != '.':
|
||||||
nameserver += '.'
|
nameserver += '.'
|
||||||
|
|
||||||
entry_attrs['nsrecord'] = nameserver
|
entry_attrs['nsrecord'] = nameserver
|
||||||
entry_attrs['idnssoamname'] = nameserver
|
entry_attrs['idnssoamname'] = nameserver
|
||||||
return dn
|
return dn
|
||||||
|
|
||||||
|
def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
|
||||||
|
if 'ip_address' in options:
|
||||||
|
nameserver = entry_attrs['idnssoamname'][0][:-1] # ends with a dot
|
||||||
|
nsparts = nameserver.split('.')
|
||||||
|
add_forward_record('.'.join(nsparts[1:]),
|
||||||
|
nsparts[0],
|
||||||
|
options['ip_address'])
|
||||||
|
|
||||||
|
return dn
|
||||||
|
|
||||||
api.register(dnszone_add)
|
api.register(dnszone_add)
|
||||||
|
|
||||||
|
|
||||||
@@ -468,6 +511,8 @@ class dnsrecord_mod_record(LDAPQuery, dnsrecord_cmd_w_record_options):
|
|||||||
|
|
||||||
entry_attrs = self.record_options_2_entry(**options)
|
entry_attrs = self.record_options_2_entry(**options)
|
||||||
|
|
||||||
|
dn = self.pre_callback(ldap, dn, entry_attrs, *keys, **options)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
(dn, old_entry_attrs) = ldap.get_entry(dn, entry_attrs.keys())
|
(dn, old_entry_attrs) = ldap.get_entry(dn, entry_attrs.keys())
|
||||||
except errors.NotFound:
|
except errors.NotFound:
|
||||||
@@ -504,6 +549,9 @@ class dnsrecord_mod_record(LDAPQuery, dnsrecord_cmd_w_record_options):
|
|||||||
def update_old_entry_callback(self, entry_attrs, old_entry_attrs):
|
def update_old_entry_callback(self, entry_attrs, old_entry_attrs):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
|
||||||
|
return dn
|
||||||
|
|
||||||
def post_callback(self, keys, entry_attrs):
|
def post_callback(self, keys, entry_attrs):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -540,6 +588,19 @@ class dnsrecord_add(LDAPCreate, dnsrecord_cmd_w_record_options):
|
|||||||
has_cli_options(options, self.no_option_msg)
|
has_cli_options(options, self.no_option_msg)
|
||||||
return super(dnsrecord_add, self).args_options_2_entry(*keys, **options)
|
return super(dnsrecord_add, self).args_options_2_entry(*keys, **options)
|
||||||
|
|
||||||
|
def _nsrecord_pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
|
||||||
|
for ns in options['nsrecord']:
|
||||||
|
is_ns_rec_resolvable(ns)
|
||||||
|
return dn
|
||||||
|
|
||||||
|
def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
|
||||||
|
for rtype in options:
|
||||||
|
rtype_cb = '_%s_pre_callback' % rtype
|
||||||
|
if hasattr(self, rtype_cb):
|
||||||
|
dn = getattr(self, rtype_cb)(ldap, dn, entry_attrs, *keys, **options)
|
||||||
|
|
||||||
|
return dn
|
||||||
|
|
||||||
def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs):
|
def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs):
|
||||||
if call_func.func_name == 'add_entry':
|
if call_func.func_name == 'add_entry':
|
||||||
if isinstance(exc, errors.DuplicateEntry):
|
if isinstance(exc, errors.DuplicateEntry):
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from tests.test_xmlrpc import objectclasses
|
|||||||
from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid
|
from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid
|
||||||
|
|
||||||
dnszone1 = u'dnszone.test'
|
dnszone1 = u'dnszone.test'
|
||||||
|
dnszone2 = u'dnszone2.test'
|
||||||
dnsres1 = u'testdnsres'
|
dnsres1 = u'testdnsres'
|
||||||
|
|
||||||
class test_dns(Declarative):
|
class test_dns(Declarative):
|
||||||
@@ -36,6 +37,7 @@ class test_dns(Declarative):
|
|||||||
api.Command['dnszone_add'](dnszone1,
|
api.Command['dnszone_add'](dnszone1,
|
||||||
idnssoamname = u'ns1.%s' % dnszone1,
|
idnssoamname = u'ns1.%s' % dnszone1,
|
||||||
idnssoarname = u'root.%s' % dnszone1,
|
idnssoarname = u'root.%s' % dnszone1,
|
||||||
|
force = True,
|
||||||
)
|
)
|
||||||
api.Command['dnszone_del'](dnszone1)
|
api.Command['dnszone_del'](dnszone1)
|
||||||
except errors.NotFound:
|
except errors.NotFound:
|
||||||
@@ -77,6 +79,7 @@ class test_dns(Declarative):
|
|||||||
'dnszone_add', [dnszone1], {
|
'dnszone_add', [dnszone1], {
|
||||||
'idnssoamname': u'ns1.%s' % dnszone1,
|
'idnssoamname': u'ns1.%s' % dnszone1,
|
||||||
'idnssoarname': u'root.%s' % dnszone1,
|
'idnssoarname': u'root.%s' % dnszone1,
|
||||||
|
'ip_address' : u'1.2.3.4',
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
expected={
|
expected={
|
||||||
@@ -107,11 +110,62 @@ class test_dns(Declarative):
|
|||||||
'dnszone_add', [dnszone1], {
|
'dnszone_add', [dnszone1], {
|
||||||
'idnssoamname': u'ns1.%s' % dnszone1,
|
'idnssoamname': u'ns1.%s' % dnszone1,
|
||||||
'idnssoarname': u'root.%s' % dnszone1,
|
'idnssoarname': u'root.%s' % dnszone1,
|
||||||
|
'ip_address' : u'1.2.3.4',
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
expected=errors.DuplicateEntry(),
|
expected=errors.DuplicateEntry(),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
dict(
|
||||||
|
desc='Try to create a zone with nonexistent NS entry',
|
||||||
|
command=(
|
||||||
|
'dnszone_add', [dnszone2], {
|
||||||
|
'idnssoamname': u'ns1.%s' % dnszone2,
|
||||||
|
'idnssoarname': u'root.%s' % dnszone2,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
expected=errors.NotFound(reason='Nameserver \'ns1.%s\' does not have a corresponding A/AAAA record' % (dnszone2)),
|
||||||
|
),
|
||||||
|
|
||||||
|
dict(
|
||||||
|
desc='Create a zone with nonexistent NS entry with --force',
|
||||||
|
command=(
|
||||||
|
'dnszone_add', [dnszone2], {
|
||||||
|
'idnssoamname': u'ns1.%s' % dnszone2,
|
||||||
|
'idnssoarname': u'root.%s' % dnszone2,
|
||||||
|
'force' : True,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
expected={
|
||||||
|
'value': dnszone2,
|
||||||
|
'summary': None,
|
||||||
|
'result': {
|
||||||
|
'dn': u'idnsname=%s,cn=dns,%s' % (dnszone2, api.env.basedn),
|
||||||
|
'idnsname': [dnszone2],
|
||||||
|
'idnszoneactive': [u'TRUE'],
|
||||||
|
'idnssoamname': [u'ns1.%s.' % dnszone2],
|
||||||
|
'nsrecord': [u'ns1.%s.' % dnszone2],
|
||||||
|
'idnssoarname': [u'root.%s.' % dnszone2],
|
||||||
|
'idnssoaserial': [fuzzy_digits],
|
||||||
|
'idnssoarefresh': [fuzzy_digits],
|
||||||
|
'idnssoaretry': [fuzzy_digits],
|
||||||
|
'idnssoaexpire': [fuzzy_digits],
|
||||||
|
'idnssoaminimum': [fuzzy_digits],
|
||||||
|
'idnsallowdynupdate': [u'FALSE'],
|
||||||
|
'objectclass': [u'top', u'idnsrecord', u'idnszone'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
dict(
|
||||||
|
desc='Delete zone %r' % dnszone2,
|
||||||
|
command=('dnszone_del', [dnszone2], {}),
|
||||||
|
expected={
|
||||||
|
'value': dnszone2,
|
||||||
|
'summary': None,
|
||||||
|
'result': {'failed': u''},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
dict(
|
dict(
|
||||||
desc='Retrieve zone %r' % dnszone1,
|
desc='Retrieve zone %r' % dnszone1,
|
||||||
@@ -286,7 +340,7 @@ class test_dns(Declarative):
|
|||||||
command=('dnsrecord_find', [dnszone1], {}),
|
command=('dnsrecord_find', [dnszone1], {}),
|
||||||
expected={
|
expected={
|
||||||
'summary': None,
|
'summary': None,
|
||||||
'count': 2,
|
'count': 3,
|
||||||
'truncated': False,
|
'truncated': False,
|
||||||
'result': [
|
'result': [
|
||||||
{
|
{
|
||||||
@@ -294,6 +348,11 @@ class test_dns(Declarative):
|
|||||||
'nsrecord': (u'ns1.dnszone.test.',),
|
'nsrecord': (u'ns1.dnszone.test.',),
|
||||||
'idnsname': [u'@'],
|
'idnsname': [u'@'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'dn': u'idnsname=ns1,idnsname=%s,cn=dns,%s' % (dnszone1, api.env.basedn),
|
||||||
|
'idnsname': [u'ns1'],
|
||||||
|
'arecord': [u'1.2.3.4'],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'dn': u'idnsname=%s,idnsname=%s,cn=dns,%s' % (dnsres1, dnszone1, api.env.basedn),
|
'dn': u'idnsname=%s,idnsname=%s,cn=dns,%s' % (dnsres1, dnszone1, api.env.basedn),
|
||||||
'idnsname': [dnsres1],
|
'idnsname': [dnsres1],
|
||||||
|
|||||||
Reference in New Issue
Block a user