mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2026-10-13 17:02:02 -05:00
Use Network Manager to configure resolv.conf
IPA used to write a custom /etc/resolv.conf. On Fedora and RHEL, NetworkManager is typically maintaining resolv.conf. On reboot or restart of the service, NM overwrites the custom settings. On systems with NM enabled, the DNS server installer now drops a config file into NM's global config directory and delegates resolv.conf to NM. On systems without NM, fall back to create /etc/resolv.conf directly. Fixes: https://pagure.io/freeipa/issue/7900 Signed-off-by: Christian Heimes <cheimes@redhat.com> Reviewed-By: Thomas Woerner <twoerner@redhat.com>
This commit is contained in:
1 parent
b79ea6a6e9
commit
80928ba6f5
6 files changed
+126
-23
No files matched your search
@@ -348,6 +348,7 @@ class BasePathNamespace:
|
||||
KDCPROXY_CONFIG = '/etc/ipa/kdcproxy/kdcproxy.conf'
|
||||
CERTMONGER = '/usr/sbin/certmonger'
|
||||
NETWORK_MANAGER_CONFIG_DIR = '/etc/NetworkManager/conf.d'
|
||||
NETWORK_MANAGER_IPA_CONF = '/etc/NetworkManager/conf.d/zzz-ipa.conf'
|
||||
IPA_CUSTODIA_CONF_DIR = '/etc/ipa/custodia'
|
||||
IPA_CUSTODIA_CONF = '/etc/ipa/custodia/custodia.conf'
|
||||
IPA_CUSTODIA_KEYS = '/etc/ipa/custodia/server.keys'
|
||||
|
||||
@@ -55,7 +55,7 @@ wellknownservices = ['certmonger', 'dirsrv', 'httpd', 'ipa', 'krb5kdc',
|
||||
'rpcbind', 'kadmin', 'sshd', 'autofs', 'rpcgssd',
|
||||
'rpcidmapd', 'pki_tomcatd', 'chronyd', 'domainname',
|
||||
'named', 'ods_enforcerd', 'ods_signerd', 'gssproxy',
|
||||
'nfs-utils', 'sssd']
|
||||
'nfs-utils', 'sssd', 'NetworkManager']
|
||||
|
||||
# The common ports for these services. This is used to wait for the
|
||||
# service to become available.
|
||||
|
||||
@@ -255,5 +255,22 @@ class BaseTaskNamespace:
|
||||
"""Tell systemd to reload config files"""
|
||||
raise NotImplementedError
|
||||
|
||||
def configure_dns_resolver(self, nameservers, searchdomains, fstore=None):
|
||||
"""Configure global DNS resolver (e.g. /etc/resolv.conf)
|
||||
|
||||
:param nameservers: list of IP addresses
|
||||
:param searchdomains: list of search domaons
|
||||
:param fstore: optional file store for backup
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def unconfigure_dns_resolver(self, fstore=None):
|
||||
"""Unconfigure global DNS resolver (e.g. /etc/resolv.conf)
|
||||
|
||||
:param fstore: optional file store for restore
|
||||
"""
|
||||
if fstore is not None and fstore.has_file(paths.RESOLV_CONF):
|
||||
fstore.restore_file(paths.RESOLV_CONF)
|
||||
|
||||
|
||||
tasks = BaseTaskNamespace()
|
||||
@@ -34,6 +34,7 @@ import errno
|
||||
import urllib
|
||||
import subprocess
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
from ctypes.util import find_library
|
||||
from functools import total_ordering
|
||||
@@ -53,6 +54,19 @@ from ipaplatform.base.tasks import BaseTaskNamespace
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
NM_IPA_CONF = textwrap.dedent("""
|
||||
# auto-generated by IPA installer
|
||||
[main]
|
||||
dns=default
|
||||
|
||||
[global-dns]
|
||||
searches={searches}
|
||||
|
||||
[global-dns-domain-*]
|
||||
servers={servers}
|
||||
""")
|
||||
|
||||
|
||||
def selinux_enabled():
|
||||
"""
|
||||
Check if SELinux is enabled.
|
||||
@@ -598,5 +612,72 @@ class RedHatTaskNamespace(BaseTaskNamespace):
|
||||
'TransferLog',
|
||||
'logs/access_log', False)
|
||||
|
||||
def configure_dns_resolver(self, nameservers, searchdomains, fstore=None):
|
||||
"""Configure global DNS resolver (e.g. /etc/resolv.conf)
|
||||
|
||||
:param nameservers: list of IP addresses
|
||||
:param searchdomains: list of search domaons
|
||||
:param fstore: optional file store for backup
|
||||
"""
|
||||
assert nameservers and isinstance(nameservers, list)
|
||||
assert searchdomains and isinstance(searchdomains, list)
|
||||
|
||||
# break circular import
|
||||
from ipaplatform.services import knownservices
|
||||
|
||||
if fstore is not None and not fstore.has_file(paths.RESOLV_CONF):
|
||||
fstore.backup_file(paths.RESOLV_CONF)
|
||||
|
||||
nm = knownservices['NetworkManager']
|
||||
if nm.is_enabled():
|
||||
logger.debug(
|
||||
"Network Manager is enabled, write %s",
|
||||
paths.NETWORK_MANAGER_IPA_CONF
|
||||
)
|
||||
# write DNS override and reload network manager to have it create
|
||||
# a new resolv.conf. The file is prefixed with ``zzz`` to
|
||||
# make it the last file. Global dns options do not stack and last
|
||||
# man standing wins.
|
||||
cfg = NM_IPA_CONF.format(
|
||||
servers=','.join(nameservers),
|
||||
searches=','.join(searchdomains)
|
||||
)
|
||||
with open(paths.NETWORK_MANAGER_IPA_CONF, 'w') as f:
|
||||
os.fchmod(f.fileno(), 0o644)
|
||||
f.write(cfg)
|
||||
# reload NetworkManager
|
||||
nm.reload_or_restart()
|
||||
else:
|
||||
# no NM running, fall back to /etc/resolv.conf
|
||||
logger.debug(
|
||||
"Network Manager is not enabled, write %s directly.",
|
||||
paths.RESOLV_CONF
|
||||
)
|
||||
cfg = [
|
||||
"# auto-generated by IPA installer",
|
||||
"search {}".format(' '.join(searchdomains)),
|
||||
]
|
||||
for nameserver in nameservers:
|
||||
cfg.append("nameserver {}".format(nameserver))
|
||||
with open(paths.RESOLV_CONF, 'w') as f:
|
||||
f.write('\n'.join(cfg))
|
||||
|
||||
def unconfigure_dns_resolver(self, fstore=None):
|
||||
"""Unconfigure global DNS resolver (e.g. /etc/resolv.conf)
|
||||
|
||||
:param fstore: optional file store for restore
|
||||
"""
|
||||
# break circular import
|
||||
from ipaplatform.services import knownservices
|
||||
|
||||
if fstore is not None and fstore.has_file(paths.RESOLV_CONF):
|
||||
fstore.restore_file(paths.RESOLV_CONF)
|
||||
|
||||
nm = knownservices['NetworkManager']
|
||||
if os.path.isfile(paths.NETWORK_MANAGER_IPA_CONF):
|
||||
os.unlink(paths.NETWORK_MANAGER_IPA_CONF)
|
||||
if nm.is_enabled():
|
||||
nm.reload_or_restart()
|
||||
|
||||
|
||||
tasks = RedHatTaskNamespace()
|
||||
@@ -51,6 +51,7 @@ import ipalib
|
||||
from ipalib import api, errors
|
||||
from ipalib.constants import IPA_CA_RECORD
|
||||
from ipaplatform import services
|
||||
from ipaplatform.tasks import tasks
|
||||
from ipaplatform.constants import constants
|
||||
from ipaplatform.paths import paths
|
||||
from ipalib.util import (validate_zonemgr_str, normalize_zonemgr,
|
||||
@@ -1007,32 +1008,25 @@ class BindInstance(service.Service):
|
||||
sysupgrade.set_upgrade_state('dns', 'server_config_to_ldap', True)
|
||||
|
||||
def __setup_resolv_conf(self):
|
||||
if not self.fstore.has_file(paths.RESOLV_CONF):
|
||||
self.fstore.backup_file(paths.RESOLV_CONF)
|
||||
|
||||
resolv_txt = "search "+self.domain+"\n"
|
||||
searchdomains = [self.domain]
|
||||
nameservers = []
|
||||
|
||||
for ip_address in self.ip_addresses:
|
||||
if ip_address.version == 4:
|
||||
resolv_txt += "nameserver 127.0.0.1\n"
|
||||
break
|
||||
nameservers.append("127.0.0.1")
|
||||
elif ip_address.version == 6:
|
||||
nameservers.append("::1")
|
||||
|
||||
for ip_address in self.ip_addresses:
|
||||
if ip_address.version == 6:
|
||||
resolv_txt += "nameserver ::1\n"
|
||||
break
|
||||
try:
|
||||
resolv_fd = open(paths.RESOLV_CONF, 'w')
|
||||
resolv_fd.seek(0)
|
||||
resolv_fd.truncate(0)
|
||||
resolv_fd.write(resolv_txt)
|
||||
resolv_fd.close()
|
||||
tasks.configure_dns_resolver(
|
||||
nameservers, searchdomains, fstore=self.fstore
|
||||
)
|
||||
except IOError as e:
|
||||
logger.error('Could not write to resolv.conf: %s', e)
|
||||
logger.error('Could not update DNS config: %s', e)
|
||||
else:
|
||||
# python DNS might have global resolver cached in this variable
|
||||
# we have to re-initialize it because resolv.conf has changed
|
||||
dns.resolver.default_resolver = None
|
||||
dns.resolver.reset_default_resolver()
|
||||
|
||||
def __generate_rndc_key(self):
|
||||
installutils.check_entropy()
|
||||
@@ -1212,11 +1206,15 @@ class BindInstance(service.Service):
|
||||
|
||||
self.dns_backup.clear_records(self.api.Backend.ldap2.isconnected())
|
||||
|
||||
for f in [paths.NAMED_CONF, paths.RESOLV_CONF]:
|
||||
try:
|
||||
self.fstore.restore_file(f)
|
||||
except ValueError as error:
|
||||
logger.debug('%s', error)
|
||||
try:
|
||||
self.fstore.restore_file(paths.NAMED_CONF)
|
||||
except ValueError as error:
|
||||
logger.debug('%s', error)
|
||||
|
||||
try:
|
||||
tasks.unconfigure_dns_resolver(fstore=self.fstore)
|
||||
except Exception:
|
||||
logger.exception("Failed to unconfigure DNS resolver")
|
||||
|
||||
installutils.rmtree(paths.BIND_LDAP_DNS_IPA_WORKDIR)
|
||||
|
||||
|
||||
@@ -424,6 +424,12 @@ class TestInstallDNSSECFirst(IntegrationTest):
|
||||
self.master.run_command(args)
|
||||
self.replicas[0].run_command(args)
|
||||
|
||||
def test_resolvconf(self):
|
||||
# check that resolv.conf contains IP address for localhost
|
||||
for host in [self.master, self.replicas[0]]:
|
||||
resolvconf = host.get_file_contents(paths.RESOLV_CONF, 'utf-8')
|
||||
assert any(ip in resolvconf for ip in ('127.0.0.1', '::1'))
|
||||
|
||||
|
||||
class TestMigrateDNSSECMaster(IntegrationTest):
|
||||
"""test DNSSEC master migration
|
||||
|
||||
Reference in new issue
Block a user