ipa/0065-Add-DNS-over-TLS-support.patch
Thomas Woerner 837c02b504 ipa-4.12.2-13
- Resolves: RHEL-67912 Add DNS over TLS Support, require bind 32:9.18.33-2 and bind-dyndb-ldap 11.11-1

Signed-off-by: Thomas Woerner <twoerner@redhat.com>
2025-02-11 17:45:23 +01:00

1448 lines
58 KiB
Diff

From 46ef43b2a68139c991883633137c0061f20222a7 Mon Sep 17 00:00:00 2001
From: Antonio Torres <antorres@redhat.com>
Date: Mon, 29 Apr 2024 11:36:52 +0200
Subject: [PATCH 1/4] Add DNS over TLS support
Add DNS over TLS support using Unbound as a local resolver. This
includes new options on both server and client side.
* `--dns-over-tls`: enable DNS over TLS support. This option is present
on both client and server. It deploys Unbound and configures BIND on
the server to receive DoT requests.
* `--dot-forwarder`: the upstream DNS server with DoT support. It must
be specified in the format `1.2.3.4#dns.server.test`
* `--dns-over-tls-key` and `--dns-over-tls-cert`: in case user prefers
to have the DoT certificate in BIND generated by themselves. If these
are empty, IPA CA is used instead to request a new certificate.
Signed-off-by: Antonio Torres <antorres@redhat.com>
Reviewed-By: Francisco Trivino <ftrivino@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Varun Mylaraiah <mvarun@redhat.com>
Reviewed-By: Pavel Brezina <pbrezina@redhat.com>
---
client/man/ipa-client-install.1 | 3 +
client/share/Makefile.am | 1 +
client/share/unbound.conf.template | 10 ++
install/share/bind.named.conf.template | 4 +
install/tools/ipa-dns-install.in | 50 +++++-
install/tools/man/ipa-dns-install.1 | 16 ++
install/tools/man/ipa-replica-install.1 | 16 ++
install/tools/man/ipa-server-install.1 | 16 ++
ipaclient/install/client.py | 89 +++++++++-
ipaplatform/base/paths.py | 4 +
ipaplatform/base/services.py | 2 +-
ipapython/ipautil.py | 13 ++
ipaserver/install/bindinstance.py | 48 +++++-
ipaserver/install/dns.py | 182 ++++++++++++++++++++-
ipaserver/install/server/__init__.py | 60 ++++++-
ipaserver/install/server/install.py | 24 ++-
ipaserver/install/server/replicainstall.py | 2 +
17 files changed, 507 insertions(+), 33 deletions(-)
create mode 100644 client/share/unbound.conf.template
diff --git a/client/man/ipa-client-install.1 b/client/man/ipa-client-install.1
index 725b11422..e6f641254 100644
--- a/client/man/ipa-client-install.1
+++ b/client/man/ipa-client-install.1
@@ -201,6 +201,9 @@ Use \fIIP_ADDRESS\fR in DNS A/AAAA record for this host. May be specified multip
.TP
\fB\-\-all\-ip\-addresses\fR
Create DNS A/AAAA record for each IP address on this host.
+.TP
+\fB\-\-dns\-over\-tls\fR
+Configure DNS over TLS.
.SS "SSSD OPTIONS"
.TP
diff --git a/client/share/Makefile.am b/client/share/Makefile.am
index bf631a22f..52f3c4dd4 100644
--- a/client/share/Makefile.am
+++ b/client/share/Makefile.am
@@ -5,6 +5,7 @@ dist_app_DATA = \
freeipa.template \
sshd_ipa.conf.template \
ssh_ipa.conf.template \
+ unbound.conf.template \
$(NULL)
epnconfdir = $(IPA_SYSCONF_DIR)
diff --git a/client/share/unbound.conf.template b/client/share/unbound.conf.template
new file mode 100644
index 000000000..166036f65
--- /dev/null
+++ b/client/share/unbound.conf.template
@@ -0,0 +1,10 @@
+server:
+ tls-cert-bundle: $TLS_CERT_BUNDLE_PATH
+ tls-upstream: yes
+ interface: 127.0.0.55
+ log-servfail: yes
+forward-zone:
+ name: "."
+ forward-tls-upstream: yes
+ forward-first: no
+ $FORWARD_ADDRS
diff --git a/install/share/bind.named.conf.template b/install/share/bind.named.conf.template
index 01b77c5ae..b64a6a4b0 100644
--- a/install/share/bind.named.conf.template
+++ b/install/share/bind.named.conf.template
@@ -21,6 +21,8 @@ options {
managed-keys-directory "$MANAGED_KEYS_DIR";
+ $NAMED_DNS_OVER_TLS_OPTIONS_CONF
+
/* user customizations of options */
include "$NAMED_CUSTOM_OPTIONS_CONF";
@@ -49,6 +51,8 @@ ${NAMED_ZONE_COMMENT}};
include "$RFC1912_ZONES";
include "$ROOT_KEY";
+$NAMED_DNS_OVER_TLS_CONF
+
/* user customization */
include "$NAMED_CUSTOM_CONF";
diff --git a/install/tools/ipa-dns-install.in b/install/tools/ipa-dns-install.in
index f1a90e7ac..0b0cd2be5 100644
--- a/install/tools/ipa-dns-install.in
+++ b/install/tools/ipa-dns-install.in
@@ -27,6 +27,7 @@ import sys
from ipaserver.install import bindinstance
from ipaserver.install import installutils
+from ipaplatform import services
from ipapython import version
from ipalib import api
from ipaplatform.paths import paths
@@ -56,6 +57,24 @@ def parse_options():
parser.add_option("--auto-forwarders", dest="auto_forwarders",
action="store_true", default=False,
help="Use DNS forwarders configured in /etc/resolv.conf")
+ parser.add_option("--dns-over-tls", dest="dns_over_tls",
+ help="Configure DNS over TLS", default=False,
+ action="store_true")
+ parser.add_option("--dot-forwarder", dest="dot_forwarders",
+ action="append",
+ default=[],
+ help="Add a DNS over TLS forwarder. "
+ "This option can be used multiple times")
+ parser.add_option("--dns-over-tls-cert", dest="dns_over_tls_cert",
+ help="Certificate to use for DNS over TLS. "
+ "If empty, a new certificate will be requested "
+ "from IPA CA")
+ parser.add_option("--dns-over-tls-key", dest="dns_over_tls_key",
+ help="Key for certificate specified "
+ "in --dns-over-tls-cert")
+ parser.add_option("--dns-policy", dest="dns_policy",
+ choices=("relaxed", "enforced"), default="relaxed",
+ help="Policy for encrypted DNS enforcement")
parser.add_option("--forward-policy", dest="forward_policy",
choices=("first", "only"), default=None,
help="DNS forwarding policy for global forwarders")
@@ -102,12 +121,37 @@ def parse_options():
elif options.auto_reverse and options.no_reverse:
parser.error("You cannot specify a --auto-reverse option together with --no-reverse")
+ if options.dns_policy == "enforced" and not options.dns_over_tls:
+ parser.error("If encrypted DNS policy is enforced, "
+ "--dns-over-tls must be specified.")
+
+ unbound = services.knownservices["unbound"]
+ if options.dns_over_tls and not unbound.is_installed():
+ parser.error(
+ "To enable DNS over TLS, package ipa-server-encrypted-dns "
+ "must be installed."
+ )
+
+ if not options.dns_over_tls and options.dot_forwarders:
+ parser.error("You cannot specify a "
+ "--dot-forwarder option without --dns-over-tls")
+ elif options.dns_over_tls and not options.dot_forwarders:
+ parser.error(
+ "You must specify --dot-forwarder "
+ "when enabling DNS over TLS")
+ elif bool(options.dns_over_tls_key) != bool(options.dns_over_tls_cert):
+ parser.error(
+ "You cannot specify a --dns-over-tls-key option "
+ "without the --dns-over-tls-cert option and vice versa")
+
if options.unattended:
if (not options.forwarders
- and not options.no_forwarders
- and not options.auto_forwarders):
+ and not options.no_forwarders
+ and not options.auto_forwarders
+ and not options.dot_forwarders):
parser.error("You must specify at least one option: "
- "--forwarder or --no-forwarders or --auto-forwarders")
+ "--forwarder or --no-forwarders or --auto-forwarders"
+ " or --dot-forwarder")
if options.kasp_db_file and not os.path.isfile(options.kasp_db_file):
parser.error("File %s does not exist" % options.kasp_db_file)
diff --git a/install/tools/man/ipa-dns-install.1 b/install/tools/man/ipa-dns-install.1
index 029001eca..6008d2028 100644
--- a/install/tools/man/ipa-dns-install.1
+++ b/install/tools/man/ipa-dns-install.1
@@ -69,6 +69,22 @@ Allow creatin of (reverse) zone even if the zone is already resolvable. Using th
.TP
\fB\-U\fR, \fB\-\-unattended\fR
An unattended installation that will never prompt for user input
+.TP
+\fB\-\-dns\-over\-tls\fR
+Configure DNS over TLS.
+.TP
+\fB\-\-dot\-forwarder\fR=\fIIP_ADDRESS#HOSTNAME\fR
+Add a DNS-over-TLS-enabled forwarder in the format of ip#hostname, e.g.: dns.example.com#1.2.3.4. This option can be used multiple times.
+.TP
+\fB\-\-dns\-over\-tls\-cert\fR=\fIFILE\fR
+Certificate to use for DNS over TLS. If empty, a new certificate will be requested from IPA CA.
+.TP
+\fB\-\-dns\-over\-tls\-key\fR=\fIFILE\fR
+Key for the certificate specified in --dns-over-tls-key.
+.TP
+\fB\-\-dns\-policy\fR=\fIrelaxed|enforced\fR
+Encrypted DNS policy. If enforced, DNS communications will only be allowed through the configured encrypted DNS methods. If relaxed,
+unencrypted DNS queries will be allowed.
.SH "DEPRECATED OPTIONS"
.TP
\fB\-p\fR \fIDM_PASSWORD\fR, \fB\-\-ds\-password\fR=\fIDM_PASSWORD\fR
diff --git a/install/tools/man/ipa-replica-install.1 b/install/tools/man/ipa-replica-install.1
index 3b1d25ba6..c55d21253 100644
--- a/install/tools/man/ipa-replica-install.1
+++ b/install/tools/man/ipa-replica-install.1
@@ -223,6 +223,22 @@ Do not automatically create DNS SSHFP records.
.TP
\fB\-\-no\-dnssec\-validation\fR
Disable DNSSEC validation on this server.
+.TP
+\fB\-\-dns\-over\-tls\fR
+Configure DNS over TLS.
+.TP
+\fB\-\-dot\-forwarder\fR=\fIIP_ADDRESS#HOSTNAME\fR
+Add a DNS-over-TLS-enabled forwarder in the format of ip#hostname, e.g.: dns.example.com#1.2.3.4. This option can be used multiple times.
+.TP
+\fB\-\-dns\-over\-tls\-cert\fR=\fIFILE\fR
+Certificate to use for DNS over TLS. If empty, a new certificate will be requested from IPA CA.
+.TP
+\fB\-\-dns\-over\-tls\-key\fR=\fIFILE\fR
+Key for the certificate specified in --dns-over-tls-key.
+.TP
+\fB\-\-dns\-policy\fR=\fIrelaxed|enforced\fR
+Encrypted DNS policy. If enforced, DNS communications will only be allowed through the configured encrypted DNS methods. If relaxed,
+unencrypted DNS queries will be allowed.
.SS "SID GENERATION OPTIONS"
.TP
diff --git a/install/tools/man/ipa-server-install.1 b/install/tools/man/ipa-server-install.1
index 215a77d6b..84d82531c 100644
--- a/install/tools/man/ipa-server-install.1
+++ b/install/tools/man/ipa-server-install.1
@@ -252,6 +252,22 @@ Disable DNSSEC validation on this server.
.TP
\fB\-\-allow\-zone\-overlap\fR
Allow creation of (reverse) zone even if the zone is already resolvable. Using this option is discouraged as it result in later problems with domain name resolution.
+.TP
+\fB\-\-dns\-over\-tls\fR
+Configure DNS over TLS.
+.TP
+\fB\-\-dot\-forwarder\fR=\fIIP_ADDRESS#HOSTNAME\fR
+Add a DNS-over-TLS-enabled forwarder in the format of ip#hostname, e.g.: dns.example.com#1.2.3.4. This option can be used multiple times.
+.TP
+\fB\-\-dns\-over\-tls\-cert\fR=\fIFILE\fR
+Certificate to use for DNS over TLS. If empty, a new certificate will be requested from IPA CA.
+.TP
+\fB\-\-dns\-over\-tls\-key\fR=\fIFILE\fR
+Key for the certificate specified in --dns-over-tls-key.
+.TP
+\fB\-\-dns\-policy\fR=\fIrelaxed|enforced\fR
+Encrypted DNS policy. If enforced, DNS communications will only be allowed through the configured encrypted DNS methods. If relaxed,
+unencrypted DNS queries will be allowed.
.SS "SID GENERATION OPTIONS"
.TP
diff --git a/ipaclient/install/client.py b/ipaclient/install/client.py
index 47a371f62..9e4d3bbe7 100644
--- a/ipaclient/install/client.py
+++ b/ipaclient/install/client.py
@@ -1002,6 +1002,11 @@ def configure_sssd_conf(
if options.dns_updates:
domain.set_option('dyndns_update', True)
+ if options.dns_over_tls:
+ server_ip = str(list(dnsutil.resolve_ip_addresses(
+ cli_server[0]))[0])
+ domain.set_option('dyndns_server', 'dns+tls://{}:853#{}'
+ .format(server_ip, cli_server[0]))
if options.all_ip_addresses:
domain.set_option('dyndns_iface', '*')
else:
@@ -1409,8 +1414,9 @@ def get_local_ipaddresses(iface=None):
return ips
-def do_nsupdate(update_txt):
+def do_nsupdate(update_txt, options, server):
logger.debug("Writing nsupdate commands to %s:", UPDATE_FILE)
+
logger.debug("%s", update_txt)
with open(UPDATE_FILE, "w") as f:
@@ -1418,12 +1424,20 @@ def do_nsupdate(update_txt):
result = False
try:
- ipautil.run([paths.NSUPDATE, '-g', UPDATE_FILE])
+ if options.dns_over_tls:
+ ipautil.run([paths.NSUPDATE, '-p', '853', '-S',
+ '-H', server, '-g', UPDATE_FILE])
+ else:
+ ipautil.run([paths.NSUPDATE, '-g', UPDATE_FILE])
result = True
except CalledProcessError as e:
logger.debug('nsupdate (GSS-TSIG) failed: %s', str(e))
try:
- ipautil.run([paths.NSUPDATE, UPDATE_FILE])
+ if options.dns_over_tls:
+ ipautil.run([paths.NSUPDATE, '-p', '853', '-S',
+ '-H', server, '-g', UPDATE_FILE])
+ else:
+ ipautil.run([paths.NSUPDATE, UPDATE_FILE])
try:
sssdconfig = SSSDConfig.SSSDConfig()
sssdconfig.import_config()
@@ -1525,6 +1539,8 @@ def update_dns(server, hostname, options):
no_matching_interface_for_ip_address_warning(update_ips)
update_txt = "debug\n"
+ if options.dns_over_tls:
+ update_txt += "server %s 853" % server
update_txt += ipautil.template_str(DELETE_TEMPLATE_A,
dict(HOSTNAME=hostname))
update_txt += ipautil.template_str(DELETE_TEMPLATE_AAAA,
@@ -1538,7 +1554,7 @@ def update_dns(server, hostname, options):
template = ADD_TEMPLATE_AAAA
update_txt += ipautil.template_str(template, sub_dict)
- if not do_nsupdate(update_txt):
+ if not do_nsupdate(update_txt, options, server):
logger.error("Failed to update DNS records.")
verify_dns_update(hostname, update_ips)
@@ -1654,6 +1670,54 @@ def client_dns(server, hostname, options):
hostname, ex)
dns_ok = False
+ # Setup DNS over TLS
+ if options.dns_over_tls:
+ # setup and enable Unbound as resolver
+ server_ip = str(list(dnsutil.resolve_ip_addresses(server))[0])
+ forward_addr = "forward-addr: %s#%s" % (server_ip, server)
+ ipautil.copy_template_file(
+ paths.UNBOUND_CONF_SRC,
+ paths.UNBOUND_CONF,
+ dict(
+ TLS_CERT_BUNDLE_PATH=os.path.join(
+ paths.OPENSSL_CERTS_DIR, "ca-bundle.crt"),
+ FORWARD_ADDRS=forward_addr
+ )
+ )
+ sr = services.knownservices["systemd-resolved"]
+ if sr.is_running():
+ sr.stop()
+ sr.disable()
+
+ nm = services.knownservices["NetworkManager"]
+ if nm.is_enabled():
+ with open(paths.NETWORK_MANAGER_IPA_CONF, "w") as f:
+ dns_none = [
+ "# auto-generated by IPA installer",
+ "[main]",
+ "dns=none\n"
+ ]
+ f.write("\n".join(dns_none))
+ nm.reload_or_restart()
+
+ # Overwrite resolv.conf to point to Unbound
+ cfg = [
+ "# auto-generated by IPA installer",
+ "search .",
+ "nameserver 127.0.0.55\n"
+ ]
+ fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
+ fstore.backup_file(paths.RESOLV_CONF)
+ with open(paths.RESOLV_CONF, 'w') as f:
+ f.write('\n'.join(cfg))
+ os.chmod(paths.RESOLV_CONF, 0o644)
+
+ services.knownservices.unbound.enable()
+ services.knownservices.unbound.restart()
+ logger.info("DNS encryption support was enabled. "
+ "Unbound is configured to listen on 127.0.0.55:53 and "
+ "forward to upstream DoT servers.")
+
if (
options.dns_updates or options.all_ip_addresses or
options.ip_addresses or not dns_ok
@@ -1661,6 +1725,7 @@ def client_dns(server, hostname, options):
update_dns(server, hostname, options)
+
def check_ip_addresses(options):
if options.ip_addresses:
for ip in options.ip_addresses:
@@ -1672,7 +1737,7 @@ def check_ip_addresses(options):
return True
-def update_ssh_keys(hostname, ssh_dir, create_sshfp):
+def update_ssh_keys(hostname, ssh_dir, options, server):
if not os.path.isdir(ssh_dir):
return
@@ -1718,10 +1783,12 @@ def update_ssh_keys(hostname, ssh_dir, create_sshfp):
logger.warning("Failed to upload host SSH public keys.")
return
- if create_sshfp:
+ if options.create_sshfp:
ttl = 1200
update_txt = 'debug\n'
+ if options.dns_over_tls:
+ update_txt += "server %s 853" % server
update_txt += 'update delete %s. IN SSHFP\nshow\nsend\n' % hostname
for pubkey in pubkeys:
sshfp = pubkey.fingerprint_dns_sha1()
@@ -1734,7 +1801,7 @@ def update_ssh_keys(hostname, ssh_dir, create_sshfp):
hostname, ttl, sshfp)
update_txt += 'show\nsend\n'
- if not do_nsupdate(update_txt):
+ if not do_nsupdate(update_txt, options, server):
logger.warning("Could not update DNS SSHFP records.")
@@ -3163,7 +3230,7 @@ def _install(options, tdict):
if not options.on_master:
client_dns(cli_server[0], hostname, options)
- update_ssh_keys(hostname, paths.SSH_CONFIG_DIR, options.create_sshfp)
+ update_ssh_keys(hostname, paths.SSH_CONFIG_DIR, options, cli_server[0])
try:
os.remove(CCACHE_FILE)
@@ -3988,6 +4055,12 @@ class ClientInstallInterface(hostname_.HostNameInstallInterface,
if value < 1:
raise ValueError("expects an integer greater than 0.")
+ dns_over_tls = knob(
+ None,
+ description="Configure DNS over TLS",
+ )
+ dns_over_tls = enroll_only(dns_over_tls)
+
request_cert = knob(
None,
deprecated=True,
diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py
index b339d2202..b2da94992 100644
--- a/ipaplatform/base/paths.py
+++ b/ipaplatform/base/paths.py
@@ -102,6 +102,8 @@ class BasePathNamespace:
NAMED_ROOT_KEY = "/etc/named.root.key"
NAMED_MANAGED_KEYS_DIR = "/var/named/dynamic"
NAMED_CRYPTO_POLICY_FILE = None
+ UNBOUND_CONF_SRC = '/usr/share/ipa/client/unbound.conf.template'
+ UNBOUND_CONF = "/etc/unbound/conf.d/zzz-ipa.conf"
NSLCD_CONF = "/etc/nslcd.conf"
NSS_LDAP_CONF = "/etc/nss_ldap.conf"
NSSWITCH_CONF = "/etc/nsswitch.conf"
@@ -225,6 +227,8 @@ class BasePathNamespace:
OPENSSL_DIR = "/etc/pki/tls"
OPENSSL_CERTS_DIR = "/etc/pki/tls/certs"
OPENSSL_PRIVATE_DIR = "/etc/pki/tls/private"
+ BIND_DNS_OVER_TLS_CRT = "/etc/pki/tls/certs/bind_dot.crt"
+ BIND_DNS_OVER_TLS_KEY = "/etc/pki/tls/private/bind_dot.key"
PK12UTIL = "/usr/bin/pk12util"
SOFTHSM2_UTIL = "/usr/bin/softhsm2-util"
SSLGET = "/usr/bin/sslget"
diff --git a/ipaplatform/base/services.py b/ipaplatform/base/services.py
index 275422e30..fcb626aa3 100644
--- a/ipaplatform/base/services.py
+++ b/ipaplatform/base/services.py
@@ -53,7 +53,7 @@ wellknownservices = [
'named', 'ods_enforcerd', 'ods_signerd', 'gssproxy',
'nfs-utils', 'sssd', 'NetworkManager', 'ipa-custodia',
'ipa-dnskeysyncd', 'ipa-otpd', 'ipa-ods-exporter',
- 'systemd-resolved',
+ 'systemd-resolved', 'unbound',
]
# The common ports for these services. This is used to wait for the
diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py
index c237d59fb..681f60655 100644
--- a/ipapython/ipautil.py
+++ b/ipapython/ipautil.py
@@ -266,6 +266,19 @@ class CheckedIPAddressLoopback(CheckedIPAddress):
file=sys.stderr)
+class IPAddressDoTForwarder(str):
+ """IPv4 or IPv6 address with added hostname as needed for DNS over TLS
+ configuration. Example: 1.2.3.4#dns.hostname.test
+ """
+ def __init__(self, addr):
+ addr_split = addr.split("#")
+ if len(addr_split) != 2 or not valid_ip(addr_split[0]):
+ raise ValueError(
+ "DoT forwarder must be in the format "
+ "of '1.2.3.4#dns.example.test'."
+ )
+
+
def valid_ip(addr):
return netaddr.valid_ipv4(addr) or netaddr.valid_ipv6(addr)
diff --git a/ipaserver/install/bindinstance.py b/ipaserver/install/bindinstance.py
index 939f5f21f..4f4ab9bbc 100644
--- a/ipaserver/install/bindinstance.py
+++ b/ipaserver/install/bindinstance.py
@@ -28,6 +28,7 @@ import re
import shutil
import sys
import time
+import textwrap
import ldap
import six
@@ -50,7 +51,7 @@ from ipapython.admintool import ScriptError
import ipalib
from ipalib import api, errors
from ipalib.constants import IPA_CA_RECORD
-from ipalib.install import dnsforwarders
+from ipalib.install import dnsforwarders, certmonger
from ipaplatform import services
from ipaplatform.tasks import tasks
from ipaplatform.constants import constants
@@ -668,14 +669,20 @@ class BindInstance(service.Service):
def setup(self, fqdn, ip_addresses, realm_name, domain_name, forwarders,
forward_policy, reverse_zones, zonemgr=None,
- no_dnssec_validation=False):
+ no_dnssec_validation=False, dns_over_tls=False,
+ dns_over_tls_cert=None, dns_over_tls_key=None,
+ dns_policy=None):
"""Setup bindinstance for installation
"""
self.setup_templating(
fqdn=fqdn,
realm_name=realm_name,
domain_name=domain_name,
- no_dnssec_validation=no_dnssec_validation
+ no_dnssec_validation=no_dnssec_validation,
+ dns_over_tls=dns_over_tls,
+ dns_over_tls_cert=dns_over_tls_cert,
+ dns_over_tls_key=dns_over_tls_key,
+ dns_policy=dns_policy
)
self.ip_addresses = ip_addresses
self.forwarders = forwarders
@@ -688,7 +695,9 @@ class BindInstance(service.Service):
self.zonemgr = normalize_zonemgr(zonemgr)
def setup_templating(
- self, fqdn, realm_name, domain_name, no_dnssec_validation=None
+ self, fqdn, realm_name, domain_name, no_dnssec_validation=None,
+ dns_over_tls=None, dns_over_tls_cert=None, dns_over_tls_key=None,
+ dns_policy=None
):
"""Setup bindinstance for templating
"""
@@ -698,6 +707,10 @@ class BindInstance(service.Service):
self.host = fqdn.split(".")[0]
self.suffix = ipautil.realm_to_suffix(self.realm)
self.no_dnssec_validation = no_dnssec_validation
+ self.dns_over_tls = dns_over_tls
+ self.dns_over_tls_cert = dns_over_tls_cert
+ self.dns_over_tls_key = dns_over_tls_key
+ self.dns_policy = dns_policy
self._setup_sub_dict()
@property
@@ -872,6 +885,24 @@ class BindInstance(service.Service):
else:
crypto_policy = "// not available"
+ if self.dns_over_tls:
+ named_tls_conf = textwrap.dedent("""\
+ tls local-tls {{
+ \tkey-file "{}";
+ \tcert-file "{}";
+ }};
+ """).format(self.dns_over_tls_key, self.dns_over_tls_cert)
+ unencrypted_iface = ("127.0.0.1" if self.dns_policy == "enforced"
+ else "any")
+ named_tls_options = textwrap.dedent("""\
+ \tlisten-on { %s; };
+ \tlisten-on tls local-tls { any; };
+ \tlisten-on-v6 tls local-tls { any; };
+ """ % unencrypted_iface)
+ else:
+ named_tls_options = ""
+ named_tls_conf = ""
+
self.sub_dict = dict(
FQDN=self.fqdn,
SERVER_ID=ipaldap.realm_to_serverid(self.realm),
@@ -891,6 +922,8 @@ class BindInstance(service.Service):
NAMED_DATA_DIR=constants.NAMED_DATA_DIR,
NAMED_ZONE_COMMENT=constants.NAMED_ZONE_COMMENT,
NAMED_DNSSEC_VALIDATION=self._get_dnssec_validation(),
+ NAMED_DNS_OVER_TLS_OPTIONS_CONF=named_tls_options,
+ NAMED_DNS_OVER_TLS_CONF=named_tls_conf,
)
def __setup_dns_container(self):
@@ -1344,6 +1377,11 @@ class BindInstance(service.Service):
self.named_conflict.unmask()
+ certmonger.stop_tracking(certfile=paths.BIND_DNS_OVER_TLS_CRT)
+ certmonger.stop_tracking(certfile=paths.BIND_DNS_OVER_TLS_KEY)
+ services.knownservices.unbound.disable()
+ services.knownservices.unbound.stop()
+
ipautil.remove_file(paths.NAMED_CONF_BAK)
ipautil.remove_file(paths.NAMED_CUSTOM_CONF)
ipautil.remove_file(paths.NAMED_CUSTOM_OPTIONS_CONF)
@@ -1357,6 +1395,8 @@ class BindInstance(service.Service):
pass
except ValueError:
pass
+ ipautil.remove_file(paths.BIND_DNS_OVER_TLS_CRT)
+ ipautil.remove_file(paths.BIND_DNS_OVER_TLS_KEY)
ipautil.remove_keytab(self.keytab)
ipautil.remove_ccache(run_as=self.service_user)
diff --git a/ipaserver/install/dns.py b/ipaserver/install/dns.py
index 47d79af9c..29ca0d2ff 100644
--- a/ipaserver/install/dns.py
+++ b/ipaserver/install/dns.py
@@ -20,14 +20,18 @@ from subprocess import CalledProcessError
from ipalib import api
from ipalib import errors
from ipalib import util
-from ipalib.install import hostname, sysrestore
+from ipalib import x509
+from ipalib.install import hostname, sysrestore, certmonger
from ipalib.install.service import enroll_only, prepare_only
from ipalib.install import dnsforwarders
+from ipalib.constants import FQDN
from ipaplatform.paths import paths
from ipaplatform.constants import constants
from ipaplatform import services
+from ipapython import admintool
from ipapython import ipautil
from ipapython import dnsutil
+from ipapython.certdb import EXTERNAL_CA_TRUST_FLAGS
from ipapython.dn import DN
from ipapython.dnsutil import check_zone_overlap
from ipapython.install import typing
@@ -37,7 +41,9 @@ from ipapython.ipautil import user_input
from ipaserver.install.installutils import get_server_ip_address
from ipaserver.install.installutils import read_dns_forwarders
from ipaserver.install.installutils import update_hosts_file
+from ipaserver.install.installutils import default_subject_base
from ipaserver.install import bindinstance
+from ipaserver.install import certs
from ipaserver.install import dnskeysyncinstance
from ipaserver.install import odsexporterinstance
from ipaserver.install import opendnssecinstance
@@ -108,6 +114,73 @@ def _disable_dnssec():
conn.update_entry(entry)
+def _setup_dns_over_tls(options):
+ if os.path.isfile(paths.IPA_CA_CRT) and not options.dns_over_tls_cert:
+ # request certificate for DNS over TLS, using IPA CA
+ cert = paths.BIND_DNS_OVER_TLS_CRT
+ key = paths.BIND_DNS_OVER_TLS_KEY
+ certmonger.request_and_wait_for_cert(
+ certpath=(cert, key),
+ principal='DNS/%s@%s' % (FQDN, api.env.realm),
+ subject=str(DN(('CN', FQDN), default_subject_base(api.env.realm))),
+ storage="FILE"
+ )
+ constants.NAMED_USER.chown(cert, gid=constants.NAMED_GROUP.gid)
+ constants.NAMED_USER.chown(key, gid=constants.NAMED_GROUP.gid)
+
+ # setup and enable Unbound as resolver
+ forward_addrs = ["# forward-addr: specify here forwarders"]
+ if options.dot_forwarders:
+ forward_addrs = ["forward-addr: %s" % fw
+ for fw in options.dot_forwarders]
+ ipautil.copy_template_file(
+ paths.UNBOUND_CONF_SRC,
+ paths.UNBOUND_CONF,
+ dict(
+ TLS_CERT_BUNDLE_PATH=os.path.join(
+ paths.OPENSSL_CERTS_DIR, "ca-bundle.crt"),
+ FORWARD_ADDRS="\n".join(forward_addrs)
+ )
+ )
+
+ sr = services.knownservices["systemd-resolved"]
+ if sr.is_running():
+ sr.stop()
+ sr.disable()
+
+ api.Command.dnsserver_mod(
+ FQDN,
+ idnsforwarders="127.0.0.55",
+ idnsforwardpolicy="first"
+ )
+
+ nm = services.knownservices["NetworkManager"]
+ if nm.is_enabled():
+ with open(paths.NETWORK_MANAGER_IPA_CONF, "w") as f:
+ dns_none = [
+ "# auto-generated by IPA installer",
+ "[main]",
+ "dns=none\n"
+ ]
+ f.write("\n".join(dns_none))
+ nm.reload_or_restart()
+
+ # Overwrite resolv.conf to point to IPA
+ cfg = [
+ "# auto-generated by IPA installer",
+ "search .",
+ "nameserver 127.0.0.1\n"
+ ]
+ fstore = sysrestore.FileStore(paths.SYSRESTORE)
+ fstore.backup_file(paths.RESOLV_CONF)
+ with open(paths.RESOLV_CONF, 'w') as f:
+ f.write('\n'.join(cfg))
+ os.chmod(paths.RESOLV_CONF, 0o644)
+
+ services.knownservices.unbound.enable()
+ services.knownservices.unbound.restart()
+
+
def package_check(exception):
if not os.path.isfile(paths.IPA_DNS_INSTALL):
raise exception(
@@ -287,9 +360,14 @@ def install_check(standalone, api, replica, options, hostname):
if options.no_forwarders:
options.forwarders = []
- elif options.forwarders or options.auto_forwarders:
+ elif (options.forwarders
+ or options.dot_forwarders or options.auto_forwarders):
if not options.forwarders:
- options.forwarders = []
+ if options.dot_forwarders:
+ options.forwarders = [fw.split("#")[0]
+ for fw in options.dot_forwarders]
+ else:
+ options.forwarders = []
if options.auto_forwarders:
options.forwarders.extend(dnsforwarders.get_nameservers())
elif standalone or not replica:
@@ -330,11 +408,46 @@ def install(standalone, replica, options, api=api):
# otherwise this is done by server/replica installer
update_hosts_file(ip_addresses, api.env.host, fstore)
+ if os.path.isfile(paths.IPA_CA_CRT) and not options.dns_over_tls_cert:
+ dot_cert = paths.BIND_DNS_OVER_TLS_CRT
+ dot_key = paths.BIND_DNS_OVER_TLS_KEY
+ elif options.dns_over_tls_cert and options.dns_over_tls_key:
+ # Check certificate validity first
+ with certs.NSSDatabase() as tmpdb:
+ tmpdb.create_db()
+ ca_certs = x509.load_certificate_list_from_file(
+ options.dns_over_tls_cert)
+ nicknames = []
+ for ca_cert in ca_certs:
+ nicknames.append(str(DN(ca_cert.subject)))
+ tmpdb.add_cert(
+ ca_cert, str(DN(ca_cert.subject)), EXTERNAL_CA_TRUST_FLAGS)
+ try:
+ for nick in nicknames:
+ tmpdb.verify_ca_cert_validity(nick)
+ except ValueError as e:
+ raise admintool.ScriptError(
+ "Not a valid CA certificate: %s" % e)
+ dot_cert = options.dns_over_tls_cert
+ dot_key = options.dns_over_tls_key
+ else:
+ raise RuntimeError(
+ "Certificate for DNS over TLS not specified "
+ "and IPA CA is not present."
+ )
+
+ if not options.forwarders and options.dot_forwarders:
+ options.forwaders = [fw.split("#")[0] for fw in options.dot_forwarders]
+
bind = bindinstance.BindInstance(fstore, api=api)
bind.setup(api.env.host, ip_addresses, api.env.realm, api.env.domain,
options.forwarders, options.forward_policy,
reverse_zones, zonemgr=options.zonemgr,
- no_dnssec_validation=options.no_dnssec_validation)
+ no_dnssec_validation=options.no_dnssec_validation,
+ dns_over_tls=options.dns_over_tls,
+ dns_over_tls_cert=dot_cert,
+ dns_over_tls_key=dot_key,
+ dns_policy=options.dns_policy)
if standalone and not options.unattended:
print("")
@@ -343,6 +456,11 @@ def install(standalone, replica, options, api=api):
print("")
bind.create_instance()
+
+ if options.dns_over_tls:
+ print("Setting up DNS over TLS")
+ _setup_dns_over_tls(options)
+
print("Restarting the web server to pick up resolv.conf changes")
services.knownservices.httpd.restart(capture_output=True)
@@ -370,6 +488,12 @@ def install(standalone, replica, options, api=api):
bind.update_system_records()
if standalone:
+ if options.dns_over_tls and options.dns_policy == "enforced":
+ dns_port = "853"
+ elif options.dns_over_tls:
+ dns_port = "53, 853"
+ else:
+ dns_port = "53"
print("==============================================================================")
print("Setup complete")
print("")
@@ -378,14 +502,22 @@ def install(standalone, replica, options, api=api):
print("")
print("\tYou must make sure these network ports are open:")
print("\t\tTCP Ports:")
- print("\t\t * 53: bind")
+ print(f"\t\t * {dns_port}: bind")
print("\t\tUDP Ports:")
- print("\t\t * 53: bind")
+ print(f"\t\t * {dns_port}: bind")
elif not standalone and replica:
print("")
bind.check_global_configuration()
print("")
+ if options.dns_over_tls:
+ policy = "enforced" if options.dns_policy == "enforced" else "relaxed"
+ print("")
+ print(("DNS encryption support was enabled "
+ "with policy '{}'.".format(policy)))
+ print(("Unbound is configured to listen on 127.0.0.55:53 and "
+ "forward to upstream DoT servers."))
+
def uninstall_check(options):
# test if server is DNSSEC key master
@@ -424,6 +556,10 @@ class DNSForwardPolicy(enum.Enum):
FIRST = 'first'
+class EncryptedDNSPolicy(enum.Enum):
+ RELAXED = 'relaxed'
+ ENFORCED = 'enforced'
+
@group
class DNSInstallInterface(hostname.HostNameInstallInterface):
"""
@@ -536,6 +672,40 @@ class DNSInstallInterface(hostname.HostNameInstallInterface):
)
no_dnssec_validation = enroll_only(no_dnssec_validation)
+ dns_over_tls = knob(
+ None,
+ description="Configure DNS over TLS",
+ )
+ dns_over_tls = enroll_only(dns_over_tls)
+
+ dot_forwarders = knob(
+ typing.List[ipautil.IPAddressDoTForwarder], None,
+ description=("Add a DNS over TLS forwarder. "
+ "This option can be used multiple times"),
+ cli_names='--dot-forwarder',
+ )
+ dot_forwarders = enroll_only(dot_forwarders)
+
+ dns_over_tls_cert = knob(
+ str, None,
+ description=("Certificate to use for DNS over TLS. "
+ "If empty, a new certificate will be "
+ "requested from IPA CA"),
+ )
+ dns_over_tls_cert = enroll_only(dns_over_tls_cert)
+
+ dns_over_tls_key = knob(
+ str, None,
+ description="Key for certificate specified in --dns-over-tls-cert",
+ )
+ dns_over_tls_key = enroll_only(dns_over_tls_key)
+
+ dns_policy = knob(
+ EncryptedDNSPolicy, 'relaxed',
+ description=("Encrypted DNS policy"),
+ )
+ dns_policy = enroll_only(dns_policy)
+
dnssec_master = False
disable_dnssec_master = False
kasp_db_file = None
diff --git a/ipaserver/install/server/__init__.py b/ipaserver/install/server/__init__.py
index 857b08f9f..c6a88585a 100644
--- a/ipaserver/install/server/__init__.py
+++ b/ipaserver/install/server/__init__.py
@@ -21,6 +21,7 @@ from ipalib.install.service import (enroll_only,
from ipapython.install import typing
from ipapython.install.core import group, knob, extend_knob
from ipapython.install.common import step
+from ipaplatform import services
from .install import validate_admin_password, validate_dm_password
from .install import get_min_idstart
@@ -442,6 +443,18 @@ class ServerInstallInterface(ServerCertificateInstallInterface,
raise RuntimeError(
"You cannot specify a --no-dnssec-validation option "
"without the --setup-dns option")
+ if self.dot_forwarders:
+ raise RuntimeError(
+ "You cannot specify a --dot-forwarder option "
+ "without the --setup-dns option")
+ if self.dns_over_tls_cert:
+ raise RuntimeError(
+ "You cannot specify a --dns-over-tls-cert option "
+ "without the --setup-dns option")
+ if self.dns_over_tls_key:
+ raise RuntimeError(
+ "You cannot specify a --dns-over-tls-key option "
+ "without the --setup-dns option")
elif self.forwarders and self.no_forwarders:
raise RuntimeError(
"You cannot specify a --forwarder option together with "
@@ -458,7 +471,32 @@ class ServerInstallInterface(ServerCertificateInstallInterface,
raise RuntimeError(
"You cannot specify a --auto-reverse option together with "
"--no-reverse")
-
+ elif self.dot_forwarders and not self.dns_over_tls:
+ raise RuntimeError(
+ "You cannot specify a --dot-forwarder option "
+ "without the --dns-over-tls option")
+ elif (self.dns_over_tls
+ and not services.knownservices["unbound"].is_installed()):
+ raise RuntimeError(
+ "To enable DNS over TLS, package ipa-server-encrypted-dns "
+ "must be installed."
+ )
+ elif self.dns_policy == "enforced" and not self.dns_over_tls:
+ raise RuntimeError(
+ "You cannot specify a --dns-policy option "
+ "without the --dns-over-tls option")
+ elif self.dns_over_tls_cert and not self.dns_over_tls:
+ raise RuntimeError(
+ "You cannot specify a --dns-over-tls-cert option "
+ "without the --dns-over-tls option")
+ elif self.dns_over_tls_key and not self.dns_over_tls:
+ raise RuntimeError(
+ "You cannot specify a --dns-over-tls-key option "
+ "without the --dns-over-tls option")
+ elif bool(self.dns_over_tls_key) != bool(self.dns_over_tls_cert):
+ raise RuntimeError(
+ "You cannot specify a --dns-over-tls-key option "
+ "without the --dns-over-tls-cert option and vice versa")
if not self.setup_adtrust:
if self.add_agents:
raise RuntimeError(
@@ -504,12 +542,18 @@ class ServerInstallInterface(ServerCertificateInstallInterface,
"In unattended mode you need to provide at least -r, "
"-p and -a options")
if self.setup_dns:
- if (not self.forwarders and
- not self.no_forwarders and
- not self.auto_forwarders):
+ if (not self.forwarders
+ and not self.no_forwarders
+ and not self.auto_forwarders
+ and not self.dot_forwarders):
raise RuntimeError(
"You must specify at least one of --forwarder, "
- "--auto-forwarders, or --no-forwarders options")
+ "--auto-forwarders, --dot-forwarder or "
+ "--no-forwarders options")
+ elif self.dns_over_tls and not self.dot_forwarders:
+ raise RuntimeError(
+ "You must specify --dot-forwarder "
+ "when enabling DNS over TLS")
any_ignore_option_true = any(
[self.ignore_topology_disconnect, self.ignore_last_of_role])
@@ -541,10 +585,12 @@ class ServerInstallInterface(ServerCertificateInstallInterface,
if self.setup_dns:
if (not self.forwarders and
not self.no_forwarders and
- not self.auto_forwarders):
+ not self.auto_forwarders
+ and not self.dot_forwarders):
raise RuntimeError(
"You must specify at least one of --forwarder, "
- "--auto-forwarders, or --no-forwarders options")
+ "--auto-forwarders, --dot-forwarder, "
+ "or --no-forwarders options")
ServerMasterInstallInterface = installs_master(ServerInstallInterface)
diff --git a/ipaserver/install/server/install.py b/ipaserver/install/server/install.py
index c39c807a9..4354683f1 100644
--- a/ipaserver/install/server/install.py
+++ b/ipaserver/install/server/install.py
@@ -1018,6 +1018,10 @@ def install(installer):
if options.setup_dns:
dns.install(False, False, options)
+ elif options.dns_over_tls:
+ service.print_msg("Warning: --dns-over-tls option "
+ "specified without --setup-dns, ignoring")
+ options.dns_over_tls = False
# Always call adtrust installer to configure SID generation
# if --setup-adtrust is not specified, only the SID part is executed
@@ -1089,12 +1093,16 @@ def install(installer):
print("\t\t * 80, 443: HTTP/HTTPS")
print("\t\t * 389, 636: LDAP/LDAPS")
print("\t\t * 88, 464: kerberos")
- if options.setup_dns:
- print("\t\t * 53: bind")
+ if options.dns_over_tls and options.dns_policy == "enforced":
+ dns_port = "853"
+ elif options.dns_over_tls:
+ dns_port = "53, 853"
+ else:
+ dns_port = "53"
+ print(f"\t\t * {dns_port}: bind")
print("\t\tUDP Ports:")
print("\t\t * 88, 464: kerberos")
- if options.setup_dns:
- print("\t\t * 53: bind")
+ print(f"\t\t * {dns_port}: bind")
if not options.no_ntp:
print("\t\t * 123: ntp")
print("")
@@ -1109,6 +1117,14 @@ def install(installer):
print("\t and servers for correct operation. You should consider "
"enabling chronyd.")
+ if options.dns_over_tls:
+ policy = "enforced" if options.dns_policy == "enforced" else "relaxed"
+ print("")
+ print(("DNS encryption support was enabled "
+ "with policy '{}'.".format(policy)))
+ print(("Unbound is configured to listen on 127.0.0.55:53 and "
+ "forward to upstream DoT servers."))
+
print("")
if setup_ca and not options.token_name:
print(("Be sure to back up the CA certificates stored in " +
diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py
index eeaaacb65..1f2c81f85 100644
--- a/ipaserver/install/server/replicainstall.py
+++ b/ipaserver/install/server/replicainstall.py
@@ -722,6 +722,8 @@ def ensure_enrolled(installer):
args.extend(("--ntp-server", server))
if installer.ntp_pool:
args.extend(("--ntp-pool", installer.ntp_pool))
+ if installer.dns_over_tls and not installer.setup_dns:
+ args.append("--dns-over-tls")
try:
# Call client install script
--
2.48.1
From 186d5f65dc57dba3bb027fa4c5c4cb1603ce305a Mon Sep 17 00:00:00 2001
From: Antonio Torres <antorres@redhat.com>
Date: Wed, 11 Dec 2024 13:38:28 +0100
Subject: [PATCH 2/4] ipatests: add tests for DNS over TLS
Signed-off-by: Antonio Torres <antorres@redhat.com>
Reviewed-By: Francisco Trivino <ftrivino@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Varun Mylaraiah <mvarun@redhat.com>
Reviewed-By: Pavel Brezina <pbrezina@redhat.com>
---
ipatests/test_integration/test_edns.py | 257 +++++++++++++++++++++++++
1 file changed, 257 insertions(+)
create mode 100644 ipatests/test_integration/test_edns.py
diff --git a/ipatests/test_integration/test_edns.py b/ipatests/test_integration/test_edns.py
new file mode 100644
index 000000000..b42570ffa
--- /dev/null
+++ b/ipatests/test_integration/test_edns.py
@@ -0,0 +1,257 @@
+#
+# Copyright (C) 2024 FreeIPA Contributors see COPYING for license
+#
+"""This covers tests for DNS over TLS related feature"""
+
+from __future__ import absolute_import
+import textwrap
+
+from ipatests.pytest_ipa.integration import tasks
+from ipatests.test_integration.base import IntegrationTest
+from ipatests.test_integration.test_dns import TestDNS
+from ipatests.pytest_ipa.integration.firewall import Firewall
+from ipaplatform.paths import paths
+
+
+class TestDNSOverTLS(IntegrationTest):
+ """Tests for DNS over TLS feature."""
+
+ topology = 'line'
+ num_replicas = 1
+ num_clients = 1
+
+ @classmethod
+ def install(cls, mh):
+ Firewall(cls.master).enable_service("dns-over-tls")
+ Firewall(cls.replicas[0]).enable_service("dns-over-tls")
+ tasks.install_packages(cls.master, ['*ipa-server-encrypted-dns'])
+ tasks.install_packages(cls.replicas[0], ['*ipa-server-encrypted-dns'])
+ tasks.install_packages(cls.clients[0], ['*ipa-client-encrypted-dns'])
+
+ def test_install_dnsovertls_invalid_ca(self):
+ """
+ This test checks that the installers throws an error
+ when invalid cert is specified.
+ """
+ bad_ca_cnf = textwrap.dedent("""
+ [ req ]
+ x509_extensions = v3_ca
+ [ v3_ca ]
+ basicConstraints = critical,CA:false
+ """)
+ self.master.put_file_contents("/bad_ca.cnf", bad_ca_cnf)
+ self.master.run_command(["openssl", "req", "-newkey", "rsa:2048",
+ "-nodes", "-keyout",
+ "/etc/pki/tls/certs/privkey-invalid.pem",
+ "-x509", "-days", "36500", "-out",
+ "/etc/pki/tls/certs/certificate-invalid.pem",
+ "-subj",
+ ("/C=ES/ST=Andalucia/L=Sevilla/O=CompanyName/"
+ "OU=IT/CN=www.example.com/"
+ "emailAddress=email@example.com"),
+ "-config", "/bad_ca.cnf"])
+ args = [
+ "--dns-over-tls",
+ "--dot-forwarder", "1.1.1.1#cloudflare-dns.com",
+ "--dns-over-tls-cert",
+ "/etc/pki/tls/certs/certificate-invalid.pem",
+ "--dns-over-tls-key",
+ "/etc/pki/tls/certs/privkey-invalid.pem"
+ ]
+ res = tasks.install_master(self.master, extra_args=args,
+ raiseonerr=False)
+ assert "Not a valid CA certificate: " in res.stderr_text
+ tasks.uninstall_master(self.master)
+
+ def test_install_dnsovertls_without_setup_dns_master(self):
+ """
+ This test installs an IPA server using the --dns-over-tls option
+ without using setup-dns option, and captures warnings that appear.
+ """
+ self.master.run_command(["ipa-server-install", "--uninstall", "-U"])
+ args = [
+ "--dns-over-tls",
+ ]
+ res = tasks.install_master(
+ self.master, extra_args=args, setup_dns=False)
+ assert ("Warning: --dns-over-tls option specified without "
+ "--setup-dns, ignoring") in res.stdout_text
+ tasks.uninstall_master(self.master)
+
+ def test_install_dnsovertls_master(self):
+ """
+ This tests installs IPA server with --dns-over-tls option.
+ """
+ args = [
+ "--dns-over-tls",
+ "--dot-forwarder", "1.1.1.1#cloudflare-dns.com",
+ ]
+ return tasks.install_master(self.master, extra_args=args)
+
+ def test_install_dnsovertls_client(self):
+ """
+ This tests installs IPA client with --dns-over-tls option.
+ """
+ self.clients[0].put_file_contents(
+ paths.RESOLV_CONF,
+ "nameserver %s" % self.master.ip
+ )
+ args = [
+ "--dns-over-tls"
+ ]
+ return tasks.install_client(self.master,
+ self.clients[0],
+ nameservers=None,
+ extra_args=args)
+
+ def test_install_dnsovertls_replica(self):
+ """
+ This tests installs IPA replica with --dns-over-tls option.
+ """
+ args = [
+ "--dns-over-tls",
+ "--dot-forwarder", "1.1.1.1#cloudflare-dns.com",
+ ]
+ return tasks.install_replica(self.master, self.replicas[0],
+ setup_dns=True, extra_args=args)
+
+ def test_queries_encrypted(self):
+ """
+ This test performs queries from each of the hosts
+ and ensures they were routed to 1.1.1.1#853 (eDNS).
+ """
+ unbound_log_cfg = textwrap.dedent("""
+ server:
+ verbosity: 3
+ log-queries: yes
+ """)
+ # Test servers first (querying to local Unbound)
+ for server in [self.master, self.replicas[0]]:
+ server.put_file_contents("/etc/unbound/conf.d/log.conf",
+ unbound_log_cfg)
+ server.run_command(["systemctl", "restart", "unbound"])
+ server.run_command(["journalctl", "--flush", "--rotate",
+ "--vacuum-time=1s"])
+ server.run_command(["dig", "freeipa.org"])
+ server.run_command(["journalctl", "-u", "unbound",
+ "--grep=1.1.1.1#853"])
+ server.run_command(["journalctl", "--flush", "--rotate",
+ "--vacuum-time=1s"])
+ # Now, test the client (redirects query to master)
+ self.clients[0].run_command(["dig", "redhat.com"])
+ self.master.run_command(["journalctl", "-u", "unbound",
+ "--grep=1.1.1.1#853"])
+
+ def test_uninstall_all(self):
+ """
+ This test ensures that all hosts can be uninstalled correctly.
+ """
+ tasks.uninstall_client(self.clients[0])
+ tasks.uninstall_replica(self.master, self.replicas[0])
+ tasks.uninstall_master(self.master)
+
+ def test_install_dnsovertls_master_external_ca(self):
+ """
+ This test ensures that IPA server can be installed
+ with DoT using an external CA.
+ """
+ self.master.run_command(["openssl", "req", "-newkey", "rsa:2048",
+ "-nodes", "-keyout",
+ "/etc/pki/tls/certs/privkey.pem", "-x509",
+ "-days", "36500", "-out",
+ "/etc/pki/tls/certs/certificate.pem", "-subj",
+ ("/C=ES/ST=Andalucia/L=Sevilla/O=CompanyName/"
+ "OU=IT/CN={}/"
+ "emailAddress=email@example.com")
+ .format(self.master.hostname)])
+ self.master.run_command(["chown", "named:named",
+ "/etc/pki/tls/certs/privkey.pem",
+ "/etc/pki/tls/certs/certificate.pem"])
+ args = [
+ "--dns-over-tls",
+ "--dot-forwarder", "1.1.1.1#cloudflare-dns.com",
+ "--dns-over-tls-cert", "/etc/pki/tls/certs/certificate.pem",
+ "--dns-over-tls-key", "/etc/pki/tls/certs/privkey.pem"
+ ]
+ return tasks.install_master(self.master, extra_args=args)
+
+ def test_enrollments_external_ca(self):
+ """
+ Test that replicas and clients can be deployed when the master
+ uses an external CA.
+ """
+ tasks.copy_files(self.master, self.clients[0],
+ ["/etc/pki/tls/certs/certificate.pem"])
+ self.clients[0].run_command(["mv",
+ "/etc/pki/tls/certs/certificate.pem",
+ "/etc/pki/ca-trust/source/anchors/"])
+ self.clients[0].run_command(["update-ca-trust", "extract"])
+ self.test_install_dnsovertls_client()
+ self.test_install_dnsovertls_replica()
+ self.test_queries_encrypted()
+
+ def test_install_dnsovertls_with_invalid_ipaddress_master(self):
+ """
+ This test installs an IPA server using the --dns-over-tls
+ option with an invalid IP address.
+ """
+ args = [
+ "--dns-over-tls",
+ "--dot-forwarder", "198.168.0.0.1#example-dns.test",
+ ]
+ res = tasks.install_master(self.master, extra_args=args,
+ raiseonerr=False)
+ assert ("--dot-forwarder invalid: DoT forwarder must be in "
+ "the format of '1.2.3.4#dns.example.test'") in res.stderr_text
+ tasks.uninstall_master(self.master)
+
+ def test_validate_DoT_options_master(self):
+ """
+ Tests that DoT options are displayed correctly on master.
+ """
+ cmdout = self.master.run_command(
+ ['ipa-server-install', '--help'])
+ assert '''--dot-forwarder=DOT_FORWARDERS
+ Add a DNS over TLS forwarder. This option can be used
+ multiple times''' in cmdout.stdout_text # noqa: E501
+ assert '''--dns-over-tls-cert=DNS_OVER_TLS_CERT
+ Certificate to use for DNS over TLS. If empty, a new
+ certificate will be requested from IPA CA''' in cmdout.stdout_text # noqa: E501
+ assert '''--dns-over-tls-key=DNS_OVER_TLS_KEY
+ Key for certificate specified in --dns-over-tls-cert''' in cmdout.stdout_text # noqa: E501
+ assert '''--dns-over-tls Configure DNS over TLS''' in cmdout.stdout_text # noqa: E501
+
+ def test_validate_DoT_options_replica(self):
+ """
+ Tests that DoT options are displayed correctly on replica.
+ """
+ cmdout = self.replicas[0].run_command(
+ ['ipa-server-install', '--help'])
+ assert '''--dot-forwarder=DOT_FORWARDERS
+ Add a DNS over TLS forwarder. This option can be used
+ multiple times''' in cmdout.stdout_text
+ assert '''--dns-over-tls-cert=DNS_OVER_TLS_CERT
+ Certificate to use for DNS over TLS. If empty, a new
+ certificate will be requested from IPA CA''' in cmdout.stdout_text # noqa: E501
+ assert '''--dns-over-tls-key=DNS_OVER_TLS_KEY
+ Key for certificate specified in --dns-over-tls-cert''' in cmdout.stdout_text # noqa: E501
+ assert '''--dns-over-tls Configure DNS over TLS''' in cmdout.stdout_text # noqa: E501
+
+ def test_validate_DoT_options_client(self):
+ """
+ Tests that DoT options are displayed correctly on client.
+ """
+ cmdout = self.clients[0].run_command(
+ ['ipa-client-install', '--help'])
+ assert '''--dns-over-tls Configure DNS over TLS''' in cmdout.stdout_text # noqa: E501
+
+
+class TestDNS_DoT(TestDNS):
+ @classmethod
+ def install(cls, mh):
+ tasks.install_packages(cls.master, ['*ipa-server-encrypted-dns'])
+ args = [
+ "--dns-over-tls",
+ "--dot-forwarder", "1.1.1.1#cloudflare-dns.com"
+ ]
+ tasks.install_master(cls.master, extra_args=args)
--
2.48.1
From a32b8fda893ae00bcd8efd91339dc8dbe35fc3cd Mon Sep 17 00:00:00 2001
From: Antonio Torres <antorres@redhat.com>
Date: Wed, 11 Dec 2024 13:35:29 +0100
Subject: [PATCH 3/4] spec: add unbound requirement and template file
Signed-off-by: Antonio Torres <antorres@redhat.com>
Reviewed-By: Francisco Trivino <ftrivino@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Varun Mylaraiah <mvarun@redhat.com>
Reviewed-By: Pavel Brezina <pbrezina@redhat.com>
---
freeipa.spec.in | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/freeipa.spec.in b/freeipa.spec.in
index 4b91aa96f..b539f51f8 100755
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -628,6 +628,7 @@ Requires: openssl-pkcs11 >= %{openssl_pkcs11_version}
# See https://bugzilla.redhat.com/show_bug.cgi?id=1825812
# RHEL 8.3+ and Fedora 32+ have 2.1
Requires: opendnssec >= 2.1.6-5
+Recommends: %{name}-server-encrypted-dns
%{?systemd_requires}
Provides: %{alt_name}-server-dns = %{version}
@@ -642,6 +643,15 @@ IPA integrated DNS server with support for automatic DNSSEC signing.
Integrated DNS server is BIND 9. OpenDNSSEC provides key management.
+%package server-encrypted-dns
+Summary: support for encrypted DNS in IPA integrated DNS server
+Requires: %{name}-client-encrypted-dns
+
+%description server-encrypted-dns
+Provides support for enabling DNS over TLS in the IPA integrated DNS
+server.
+
+
%package server-trust-ad
Summary: Virtual package to install packages required for Active Directory trusts
Requires: %{name}-server = %{version}-%{release}
@@ -722,6 +732,7 @@ Requires: libnfsidmap
Requires: (nfs-utils or nfsv4-client-utils)
Requires: sssd-tools >= %{sssd_version}
Requires(post): policycoreutils
+Recommends: %{name}-client-encrypted-dns
# https://pagure.io/freeipa/issue/8530
Recommends: libsss_sudo
@@ -763,6 +774,14 @@ If your network uses IPA for authentication, this package should be
installed on every client machine.
This package provides command-line tools for IPA administrators.
+%package client-encrypted-dns
+Summary: Enable encrypted DNS support for clients
+Requires: unbound
+
+%description client-encrypted-dns
+This package enables support for installing clients with encrypted DNS
+via DNS over TLS.
+
%package client-samba
Summary: Tools to configure Samba on IPA client
Group: System Environment/Base
@@ -1724,6 +1743,10 @@ fi
%attr(644,root,root) %{_unitdir}/ipa-ods-exporter.socket
%attr(644,root,root) %{_unitdir}/ipa-ods-exporter.service
+%files server-encrypted-dns
+%doc README.md Contributors.txt
+%license COPYING
+
%files server-trust-ad
%doc README.md Contributors.txt
%license COPYING
@@ -1783,6 +1806,10 @@ fi
%attr(600,root,root) %config(noreplace) %{_sysconfdir}/ipa/epn.conf
%attr(644,root,root) %config(noreplace) %{_sysconfdir}/ipa/epn/expire_msg.template
+%files client-encrypted-dns
+%doc README.md Contributors.txt
+%license COPYING
+
%files -n python3-ipaclient
%doc README.md Contributors.txt
%license COPYING
--
2.48.1