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>
This commit is contained in:
Antonio Torres authored and Florence Blanc-Renaud committed 2025-02-11 09:09:22 +01:00
1 parent 4952dff42d
commit 3de127433c
17 files changed
+507 -33

No files matched your search

+3
View File
@@ -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
+1
View File
@@ -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)
+10
View File
@@ -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
+4
View File
@@ -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";
+47 -3
View File
@@ -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)
+16
View File
@@ -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
+16
View File
@@ -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
+16
View File
@@ -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
+81 -8
View File
@@ -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,
+4
View File
@@ -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"
+1 -1
View File
@@ -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
+13
View File
@@ -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)
+44 -4
View File
@@ -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)
+176 -6
View File
@@ -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
+53 -7
View File
@@ -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)
+20 -4
View File
@@ -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 " +
@@ -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