mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
Generate CNAMEs for TXT+URI location krb records
The IPA location system relies on DNS record priorities in order to give higher precedence to servers from the same location. For Kerberos, this is done by redirecting generic SRV records (e.g. _kerberos._udp.[domain].) to location-aware records (e.g. _kerberos._udp.[location]._locations.[domain].) using CNAMEs. This commit applies the same logic for URI records. URI location-aware record were created, but there were no redirection from generic URI records. It was causing them to be ignored in practice. Kerberos URI and TXT records have the same name: "_kerberos". However, CNAME records cannot coexist with any other record type. To avoid this conflict, the generic TXT realm record was replaced by location-aware records, even if the content of these records is the same for all locations. Fixes: https://pagure.io/freeipa/issue/9257 Signed-off-by: Julien Rische <jrische@redhat.com> Reviewed-By: Rob Crittenden <rcritten@redhat.com>
This commit is contained in:
parent
3d6d7e9fdf
commit
673d2b82d0
@ -42,3 +42,4 @@ plugin: update_dnsserver_configuration_into_ldap
|
||||
plugin: update_ldap_server_list
|
||||
plugin: update_dna_shared_config
|
||||
plugin: update_unhashed_password
|
||||
plugin: update_krb_uri_txt_records_for_locations
|
||||
|
@ -70,6 +70,10 @@ IPA_DEFAULT_NTP_SRV_REC = (
|
||||
(DNSName("_ntp._udp"), 123),
|
||||
)
|
||||
|
||||
IPA_DEFAULT_KRB_TXT_REC = (
|
||||
(DNSName('_kerberos'), "\"{realm}\""),
|
||||
)
|
||||
|
||||
CA_RECORDS_DNS_TIMEOUT = 15 # timeout in seconds
|
||||
|
||||
|
||||
@ -210,10 +214,15 @@ class IPASystemRecords:
|
||||
r_name, rd.rdtype, create=True)
|
||||
rdataset.add(rd, ttl=self.TTL)
|
||||
|
||||
def __add_kerberos_txt_rec(self, zone_obj):
|
||||
def __add_kerberos_txt_rec(self, zone_obj, location=None):
|
||||
# FIXME: with external DNS, this should generate records for all
|
||||
# realmdomains
|
||||
r_name = DNSName('_kerberos') + self.domain_abs
|
||||
if location:
|
||||
suffix = self.__get_location_suffix(location)
|
||||
else:
|
||||
suffix = self.domain_abs
|
||||
|
||||
r_name = DNSName('_kerberos') + suffix
|
||||
rd = rdata.from_text(rdataclass.IN, rdatatype.TXT,
|
||||
self.api_instance.env.realm)
|
||||
rdataset = zone_obj.get_rdataset(
|
||||
@ -233,7 +242,7 @@ class IPASystemRecords:
|
||||
hostname_abs = DNSName(hostname).make_absolute()
|
||||
|
||||
if include_kerberos_realm:
|
||||
self.__add_kerberos_txt_rec(zone_obj)
|
||||
self.__add_kerberos_txt_rec(zone_obj, location=None)
|
||||
|
||||
# get master records
|
||||
if include_master_role:
|
||||
@ -271,7 +280,8 @@ class IPASystemRecords:
|
||||
|
||||
def _get_location_dns_records_for_server(
|
||||
self, zone_obj, hostname, locations,
|
||||
roles=None, include_master_role=True):
|
||||
roles=None, include_master_role=True,
|
||||
include_kerberos_realm=True):
|
||||
server = self.servers_data[hostname]
|
||||
if roles:
|
||||
eff_roles = server['roles'] & roles
|
||||
@ -286,6 +296,9 @@ class IPASystemRecords:
|
||||
else:
|
||||
priority = self.PRIORITY_LOW
|
||||
|
||||
if include_kerberos_realm:
|
||||
self.__add_kerberos_txt_rec(zone_obj, location)
|
||||
|
||||
if include_master_role:
|
||||
self.__add_srv_records(
|
||||
zone_obj,
|
||||
@ -401,7 +414,9 @@ class IPASystemRecords:
|
||||
return zone_obj
|
||||
|
||||
def get_locations_records(
|
||||
self, servers=None, roles=None, include_master_role=True):
|
||||
self, servers=None, roles=None, include_master_role=True,
|
||||
include_kerberos_realm=True
|
||||
):
|
||||
"""
|
||||
Generate IPA location records for specific servers and roles.
|
||||
:param servers: list of server which will be used in records,
|
||||
@ -423,7 +438,8 @@ class IPASystemRecords:
|
||||
self._get_location_dns_records_for_server(
|
||||
zone_obj, server,
|
||||
locations, roles=roles,
|
||||
include_master_role=include_master_role)
|
||||
include_master_role=include_master_role,
|
||||
include_kerberos_realm=include_kerberos_realm)
|
||||
return zone_obj
|
||||
|
||||
def update_base_records(self):
|
||||
@ -437,9 +453,11 @@ class IPASystemRecords:
|
||||
success = []
|
||||
names_requiring_cname_templates = set(
|
||||
rec[0].derelativize(self.domain_abs) for rec in (
|
||||
IPA_DEFAULT_MASTER_SRV_REC +
|
||||
IPA_DEFAULT_ADTRUST_SRV_REC +
|
||||
IPA_DEFAULT_NTP_SRV_REC
|
||||
IPA_DEFAULT_MASTER_SRV_REC
|
||||
+ IPA_DEFAULT_MASTER_URI_REC
|
||||
+ IPA_DEFAULT_KRB_TXT_REC
|
||||
+ IPA_DEFAULT_ADTRUST_SRV_REC
|
||||
+ IPA_DEFAULT_NTP_SRV_REC
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -551,3 +551,111 @@ class update_dnsserver_configuration_into_ldap(DNSUpdater):
|
||||
"created in LDAP database")
|
||||
sysupgrade.set_upgrade_state('dns', 'server_config_to_ldap', True)
|
||||
return False, []
|
||||
|
||||
|
||||
@register()
|
||||
class update_krb_uri_txt_records_for_locations(DNSUpdater):
|
||||
|
||||
backup_filename = u'dns-krb-uri-txt-records-for-locations-%Y-%m-%d-%H-%M-'\
|
||||
u'%S.ldif'
|
||||
|
||||
def execute(self, **options):
|
||||
ldap = self.api.Backend.ldap2
|
||||
|
||||
if not dns_container_exists(ldap):
|
||||
return False, []
|
||||
|
||||
locations = []
|
||||
|
||||
for location in self.api.Command.location_find()['result']:
|
||||
locations.append(str(location['idnsname'][0]))
|
||||
|
||||
if not locations:
|
||||
return False, []
|
||||
|
||||
tmpl_class_attr = 'objectClass'
|
||||
tmpl_class_value = 'idnsTemplateObject'
|
||||
cname_tmpl_attr = 'idnsTemplateAttribute;cnamerecord'
|
||||
cname_tmpl_value = '_kerberos.\\{substitutionvariable_ipalocation\\}.'\
|
||||
'_locations'
|
||||
|
||||
domain = self.env.domain
|
||||
realm = self.env.realm
|
||||
|
||||
dns_updates = []
|
||||
|
||||
main_krb_rec = '_kerberos.' + domain + '.'
|
||||
main_krb_rec_dn = DN(('idnsname', '_kerberos'),
|
||||
('idnsname', domain + '.'),
|
||||
self.env.container_dns, self.env.basedn)
|
||||
|
||||
try:
|
||||
entry = ldap.get_entry(main_krb_rec_dn, [
|
||||
tmpl_class_attr, cname_tmpl_attr,
|
||||
])
|
||||
except errors.NotFound:
|
||||
logger.debug('DNS: %s does not exist, there is no %s record to '
|
||||
'convert into CNAME', main_krb_rec_dn, main_krb_rec)
|
||||
else:
|
||||
cname_updates = []
|
||||
|
||||
if tmpl_class_value not in entry.get(tmpl_class_attr, []):
|
||||
logger.debug('DNS: convert %s into a CNAME record',
|
||||
main_krb_rec)
|
||||
cname_updates.append({
|
||||
'action': 'add',
|
||||
'attr': tmpl_class_attr,
|
||||
'value': tmpl_class_value,
|
||||
})
|
||||
|
||||
if cname_tmpl_value not in entry.get(cname_tmpl_attr, []):
|
||||
logger.debug('DNS: update %s CNAME record to point to location '
|
||||
'records', main_krb_rec)
|
||||
cname_updates.append({
|
||||
'action': 'add',
|
||||
'attr': cname_tmpl_attr,
|
||||
'value': cname_tmpl_value,
|
||||
})
|
||||
|
||||
if cname_updates:
|
||||
dns_updates.append({
|
||||
'dn': main_krb_rec_dn,
|
||||
'updates': cname_updates,
|
||||
})
|
||||
else:
|
||||
logger.debug('DNS: %s is already a valid CNAME record',
|
||||
main_krb_rec)
|
||||
|
||||
for location in locations:
|
||||
location_krb_rec = '_kerberos.' + location + '._locations.' \
|
||||
+ domain + '.'
|
||||
location_krb_rec_dn = DN(('idnsname',
|
||||
'_kerberos.' + location + '._locations'),
|
||||
('idnsname', domain + '.'),
|
||||
self.env.container_dns, self.env.basedn)
|
||||
|
||||
try:
|
||||
entry = ldap.get_entry(location_krb_rec_dn, ['tXTRecord'])
|
||||
except errors.NotFound:
|
||||
logger.debug('DNS: %s does not exist, there is no %s URI '
|
||||
'record to rely on to create a TXT record',
|
||||
location_krb_rec_dn, location_krb_rec)
|
||||
else:
|
||||
if 'tXTRecord' in entry:
|
||||
logger.debug('DNS: there already is a %s TXT record',
|
||||
location_krb_rec)
|
||||
else:
|
||||
logger.debug('DNS: add %s location-aware TXT record',
|
||||
location_krb_rec)
|
||||
dns_updates.append({
|
||||
'dn': location_krb_rec_dn,
|
||||
'updates': [
|
||||
{
|
||||
'action': 'addifnew',
|
||||
'attr': 'tXTRecord',
|
||||
'value': f'"{realm}"',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
return False, dns_updates
|
||||
|
@ -20,6 +20,7 @@ from ipapython.dn import DN
|
||||
from ipapython.ipautil import template_str
|
||||
from ipaserver.install import bindinstance
|
||||
from ipaserver.install.sysupgrade import STATEFILE_FILE
|
||||
from ipalib.constants import DEFAULT_CONFIG
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
from ipatests.pytest_ipa.integration import tasks
|
||||
|
||||
@ -98,6 +99,24 @@ def clear_sysupgrade(host, *sections):
|
||||
host.put_file_contents(statefile, out.getvalue())
|
||||
|
||||
|
||||
def get_main_krb_rec_dn(domain):
|
||||
return DN(
|
||||
('idnsname', '_kerberos'),
|
||||
('idnsname', domain.name + '.'),
|
||||
dict(DEFAULT_CONFIG)['container_dns'],
|
||||
domain.basedn,
|
||||
)
|
||||
|
||||
|
||||
def get_location_krb_rec_dn(domain, location):
|
||||
return DN(
|
||||
('idnsname', '_kerberos.' + location + '._locations'),
|
||||
('idnsname', domain.name + '.'),
|
||||
dict(DEFAULT_CONFIG)['container_dns'],
|
||||
domain.basedn,
|
||||
)
|
||||
|
||||
|
||||
class TestUpgrade(IntegrationTest):
|
||||
"""
|
||||
Test ipa-server-upgrade.
|
||||
@ -111,6 +130,73 @@ class TestUpgrade(IntegrationTest):
|
||||
tasks.install_master(cls.master)
|
||||
tasks.install_dns(cls.master)
|
||||
|
||||
@pytest.fixture
|
||||
def setup_locations(self):
|
||||
realm = self.master.domain.realm
|
||||
|
||||
_locations = []
|
||||
|
||||
def _setup_locations(locations):
|
||||
_locations = locations
|
||||
|
||||
ldap = self.master.ldap_connect()
|
||||
|
||||
for location in locations:
|
||||
self.master.run_command(['ipa', 'location-add', location])
|
||||
self.master.run_command([
|
||||
'ipa',
|
||||
'server-mod',
|
||||
'--location=' + locations[0],
|
||||
self.master.hostname,
|
||||
])
|
||||
|
||||
main_krb_rec = ldap.get_entry(
|
||||
get_main_krb_rec_dn(self.master.domain),
|
||||
)
|
||||
main_krb_rec['objectClass'].remove('idnsTemplateObject')
|
||||
del main_krb_rec['idnsTemplateAttribute;cnamerecord']
|
||||
ldap.update_entry(main_krb_rec)
|
||||
|
||||
for location in locations:
|
||||
location_krb_rec = ldap.get_entry(
|
||||
get_location_krb_rec_dn(self.master.domain, location),
|
||||
)
|
||||
del location_krb_rec['tXTRecord']
|
||||
ldap.update_entry(location_krb_rec)
|
||||
|
||||
yield _setup_locations
|
||||
|
||||
ldap = self.master.ldap_connect()
|
||||
|
||||
modified = False
|
||||
main_krb_rec = ldap.get_entry(get_main_krb_rec_dn(self.master.domain))
|
||||
if 'idnsTemplateObject' not in main_krb_rec['objectClass']:
|
||||
main_krb_rec['objectClass'].append('idnsTemplateObject')
|
||||
modified = True
|
||||
if 'idnsTemplateAttribute;cnamerecord' not in main_krb_rec:
|
||||
main_krb_rec['idnsTemplateAttribute;cnamerecord'] = \
|
||||
'_kerberos.\\{substitutionvariable_ipalocation\\}._locations'
|
||||
modified = True
|
||||
if modified:
|
||||
ldap.update_entry(main_krb_rec)
|
||||
|
||||
for location in _locations:
|
||||
location_krb_rec = ldap.get_entry(
|
||||
get_location_krb_rec_dn(self.master.domain, location),
|
||||
)
|
||||
if 'tXTRecord' not in location_krb_rec:
|
||||
location_krb_rec['tXTRecord'] = f'"{realm}"'
|
||||
ldap.update_entry(location_krb_rec)
|
||||
|
||||
self.master.run_command([
|
||||
'ipa',
|
||||
'server-mod',
|
||||
'--location=',
|
||||
self.master.hostname,
|
||||
])
|
||||
for location in _locations:
|
||||
self.master.run_command(['ipa', 'location-del', location])
|
||||
|
||||
def test_invoke_upgrader(self):
|
||||
cmd = self.master.run_command(['ipa-server-upgrade'],
|
||||
raiseonerr=False)
|
||||
@ -334,3 +420,38 @@ class TestUpgrade(IntegrationTest):
|
||||
assert "False" in result.stdout_text
|
||||
finally:
|
||||
self.master.run_command(["rmdir", kra_path])
|
||||
|
||||
def test_krb_uri_txt_to_cname(self, setup_locations):
|
||||
"""Test that ipa-server-upgrade correctly updates Kerberos DNS records
|
||||
|
||||
Test for https://pagure.io/freeipa/issue/9257
|
||||
Kerberos URI and TXT DNS records should be location-aware in case the
|
||||
server is part of a location, in order for DNS discovery to prioritize
|
||||
servers from the same location. This means that for such servers the
|
||||
_kerberos record should be a CNAME one pointing to the appropriate set
|
||||
of location-aware records.
|
||||
"""
|
||||
realm = self.master.domain.realm
|
||||
locations = ['a', 'b']
|
||||
|
||||
setup_locations(locations)
|
||||
|
||||
self.master.run_command(['ipa-server-upgrade'])
|
||||
|
||||
ldap = self.master.ldap_connect()
|
||||
|
||||
main_krb_rec = ldap.get_entry(
|
||||
get_main_krb_rec_dn(self.master.domain),
|
||||
)
|
||||
assert 'idnsTemplateObject' in main_krb_rec['objectClass']
|
||||
assert len(main_krb_rec['idnsTemplateAttribute;cnamerecord']) == 1
|
||||
assert main_krb_rec['idnsTemplateAttribute;cnamerecord'][0] \
|
||||
== '_kerberos.\\{substitutionvariable_ipalocation\\}._locations'
|
||||
|
||||
for location in locations:
|
||||
location_krb_rec = ldap.get_entry(
|
||||
get_location_krb_rec_dn(self.master.domain, location),
|
||||
)
|
||||
assert 'tXTRecord' in location_krb_rec
|
||||
assert len(location_krb_rec['tXTRecord']) == 1
|
||||
assert location_krb_rec['tXTRecord'][0] == f'"{realm}"'
|
||||
|
Loading…
Reference in New Issue
Block a user