mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2026-10-25 14:40:16 -05:00
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:
1 parent
4952dff42d
commit
3de127433c
17 files changed
+507
-33
No files matched your search
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user