ipaserver: Add support for DNS over TLS

This change adds support for DNS over TLS to the ipaserver role.

New variables

ipaserver_dot_forwarders
    List of DNS over TLS forwarders. Required if ipaserver_dns_over_tls
    is enabled. (list of strings)
    required: false
ipaserver_dns_over_tls | ipaclient_dns_over_tls
    Configure DNS over TLS. Requires FreeIPA version 4.12.5 or later.
    (bool, default: false)
    required: false
ipaserver_dns_over_tls_cert
    Certificate to use for DNS over TLS. If empty, a new certificate will
    be requested from IPA CA. (string)
    required: false
ipaserver_dns_over_tls_key
    Key for certificate specified in ipaserver_dns_over_tls_cert. (string)
    required: false
ipaserver_dns_policy
    Encrypted DNS policy. Only usable if `ipaserver_dns_over_tls` is
    enabled. (choice: relaxed, enforced, default: relaxed)
    required: false

New distribution specific variable

ipaserver_packages_dot
    List of IPA packages needed for DNS over TLS.
This commit is contained in:
Thomas Woerner
2025-07-01 14:31:51 +02:00
parent 7a23c668fc
commit e2317f304c
12 changed files with 246 additions and 39 deletions

View File

@@ -343,6 +343,12 @@ Variable | Description | Required
`ipaserver_auto_forwarders` | Add DNS forwarders configured in /etc/resolv.conf to the list of forwarders used by IPA DNS. (bool, default: false) | no
`ipaserver_forward_policy` | DNS forwarding policy for global forwarders specified using other options. (choice: first, only) | no
`ipaserver_no_dnssec_validation` | Disable DNSSEC validation on this server. (bool, default: false) | no
`ipaserver_dot_forwarders` | List of DNS over TLS forwarders. Required if `ipaserver_dns_over_tls` is enabled. (list of strings) | no
`ipaserver_dns_over_tls` \| `ipaclient_dns_over_tls` | Configure DNS over TLS. Requires FreeIPA version 4.12.5 or later. (bool, default: false) | no
`ipaserver_dns_over_tls_cert` | Certificate to use for DNS over TLS. If empty, a new certificate will be requested from IPA CA. (string) | no
`ipaserver_dns_over_tls_key` | Key for certificate specified in `ipaserver_dns_over_tls_cert`. (string) | no
`ipaserver_dns_policy` | Encrypted DNS policy. Only usable if `ipaserver_dns_over_tls` is enabled. (choice: relaxed, enforced, default: relaxed) | no
AD trust Variables
------------------

View File

@@ -174,6 +174,32 @@ options:
type: bool
default: no
required: no
dot_forwarders:
description: List of DNS over TLS forwarders
type: list
elements: str
default: []
required: no
dns_over_tls:
description: Configure DNS over TLS
type: bool
default: no
required: no
dns_over_tls_cert:
description:
Certificate to use for DNS over TLS. If empty, a new
certificate will be requested from IPA CA
type: str
required: no
dns_over_tls_key:
description: Key for certificate specified in dns_over_tls_cert
type: str
required: no
dns_policy:
description: Encrypted DNS policy
type: str
choices: ['relaxed', 'enforced']
default: 'relaxed'
enable_compat:
description: Enable support for trusted domains for old clients
type: bool
@@ -280,6 +306,15 @@ def main():
choices=['first', 'only'], default=None),
no_dnssec_validation=dict(required=False, type='bool',
default=False),
dot_forwarders=dict(required=False, type='list', elements='str',
default=[]),
dns_over_tls=dict(required=False, type='bool',
default=False),
dns_over_tls_cert=dict(required=False, type='str'),
dns_over_tls_key=dict(required=False, type='str'),
dns_policy=dict(required=False, type='str',
choices=['relaxed', 'enforced'],
default='relaxed'),
# ad trust
enable_compat=dict(required=False, type='bool', default=False),
netbios_name=dict(required=False, type='str'),
@@ -360,6 +395,11 @@ def main():
options.forward_policy = ansible_module.params.get('forward_policy')
options.no_dnssec_validation = ansible_module.params.get(
'no_dnssec_validation')
options.dot_forwarders = ansible_module.params.get('dot_forwarders')
options.dns_over_tls = ansible_module.params.get('dns_over_tls')
options.dns_over_tls_cert = ansible_module.params.get('dns_over_tls_cert')
options.dns_over_tls_key = ansible_module.params.get('dns_over_tls_key')
options.dns_policy = ansible_module.params.get('dns_policy')
# ad trust
options.enable_compat = ansible_module.params.get('enable_compat')
options.netbios_name = ansible_module.params.get('netbios_name')

View File

@@ -83,6 +83,32 @@ options:
type: bool
default: no
required: no
dot_forwarders:
description: List of DNS over TLS forwarders
type: list
elements: str
default: []
required: no
dns_over_tls:
description: Configure DNS over TLS
type: bool
default: no
required: no
dns_over_tls_cert:
description:
Certificate to use for DNS over TLS. If empty, a new
certificate will be requested from IPA CA
type: str
required: no
dns_over_tls_key:
description: Key for certificate specified in dns_over_tls_cert
type: str
required: no
dns_policy:
description: Encrypted DNS policy
type: str
choices: ['relaxed', 'enforced']
default: 'relaxed'
dns_ip_addresses:
description: The dns ip_addresses setting
type: list
@@ -107,9 +133,13 @@ from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_server import (
check_imports, AnsibleModuleLog, setup_logging, options, paths, dns,
ansible_module_get_parsed_ip_addresses, sysrestore, api_Backend_ldap2,
redirect_stdout, bindinstance
redirect_stdout
)
# pylint: disable=unused-import
from ansible.module_utils.ansible_ipa_server import bindinstance # noqa: F401
# pylint: enable=unused-import
def main():
ansible_module = AnsibleModule(
@@ -130,6 +160,14 @@ def main():
default='first'),
no_dnssec_validation=dict(required=False, type='bool',
default=False),
dot_forwarders=dict(required=False, type='list', elements='str',
default=[]),
dns_over_tls=dict(required=False, type='bool', default=False),
dns_over_tls_cert=dict(required=False, type='str'),
dns_over_tls_key=dict(required=False, type='str'),
dns_policy=dict(required=False, type='str',
choices=['relaxed', 'enforced'],
default='relaxed'),
# additional
dns_ip_addresses=dict(required=True, type='list', elements='str'),
dns_reverse_zones=dict(required=True, type='list', elements='str'),
@@ -158,6 +196,11 @@ def main():
options.forward_policy = ansible_module.params.get('forward_policy')
options.no_dnssec_validation = ansible_module.params.get(
'no_dnssec_validation')
options.dot_forwarders = ansible_module.params.get('dot_forwarders')
options.dns_over_tls = ansible_module.params.get('dns_over_tls')
options.dns_over_tls_cert = ansible_module.params.get('dns_over_tls_cert')
options.dns_over_tls_key = ansible_module.params.get('dns_over_tls_key')
options.dns_policy = ansible_module.params.get('dns_policy')
# additional
dns.ip_addresses = ansible_module_get_parsed_ip_addresses(
ansible_module, 'dns_ip_addresses')
@@ -165,25 +208,16 @@ def main():
# init ##################################################################
fstore = sysrestore.FileStore(paths.SYSRESTORE)
# pylint: disable=unused-variable
fstore = sysrestore.FileStore(paths.SYSRESTORE) # noqa: F841
# pylint: enable=unused-variable
api_Backend_ldap2(options.host_name, options.setup_ca, connect=True)
# setup dns #############################################################
with redirect_stdout(ansible_log):
if options.setup_dns:
dns.install(False, False, options)
else:
# Create a BIND instance
bind = bindinstance.BindInstance(fstore)
bind.set_output(ansible_log)
bind.setup(options.host_name, options.ip_addresses,
options.realm_name,
options.domain_name, (), 'first', (),
zonemgr=options.zonemgr,
no_dnssec_validation=options.no_dnssec_validation)
bind.create_file_with_system_records()
dns.install(False, False, options)
# done ##################################################################

View File

@@ -265,6 +265,32 @@ options:
type: bool
default: no
required: no
dot_forwarders:
description: List of DNS over TLS forwarders
type: list
elements: str
default: []
required: no
dns_over_tls:
description: Configure DNS over TLS
type: bool
default: no
required: no
dns_over_tls_cert:
description:
Certificate to use for DNS over TLS. If empty, a new
certificate will be requested from IPA CA
type: str
required: no
dns_over_tls_key:
description: Key for certificate specified in dns_over_tls_cert
type: str
required: no
dns_policy:
description: Encrypted DNS policy
type: str
choices: ['relaxed', 'enforced']
default: 'relaxed'
enable_compat:
description: Enable support for trusted domains for old clients
type: bool
@@ -312,7 +338,8 @@ from ansible.module_utils.ansible_ipa_server import (
check_dirsrv, ScriptError, get_fqdn, verify_fqdn, BadHostError,
validate_domain_name, load_pkcs12, IPA_PYTHON_VERSION,
encode_certificate, check_available_memory, getargspec, adtrustinstance,
get_min_idstart, SerialNumber
get_min_idstart, SerialNumber, services, service,
CLIENT_SUPPORTS_NO_DNSSEC_VALIDATION
)
from ansible.module_utils import six
@@ -396,6 +423,14 @@ def main():
choices=['first', 'only'], default=None),
no_dnssec_validation=dict(required=False, type='bool',
default=False),
dot_forwarders=dict(required=False, type='list', elements='str',
default=[]),
dns_over_tls=dict(required=False, type='bool', default=False),
dns_over_tls_cert=dict(required=False, type='str'),
dns_over_tls_key=dict(required=False, type='str'),
dns_policy=dict(required=False, type='str',
choices=['relaxed', 'enforced'],
default='relaxed'),
# ad trust
enable_compat=dict(required=False, type='bool', default=False),
netbios_name=dict(required=False, type='str'),
@@ -482,6 +517,11 @@ def main():
options.forward_policy = ansible_module.params.get('forward_policy')
options.no_dnssec_validation = ansible_module.params.get(
'no_dnssec_validation')
options.dot_forwarders = ansible_module.params.get('dot_forwarders')
options.dns_over_tls = ansible_module.params.get('dns_over_tls')
options.dns_over_tls_cert = ansible_module.params.get('dns_over_tls_cert')
options.dns_over_tls_key = ansible_module.params.get('dns_over_tls_key')
options.dns_policy = ansible_module.params.get('dns_policy')
# ad trust
options.enable_compat = ansible_module.params.get('enable_compat')
options.netbios_name = ansible_module.params.get('netbios_name')
@@ -603,6 +643,14 @@ def main():
raise RuntimeError(
"You cannot specify a --no-dnssec-validation 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 "
@@ -619,7 +667,31 @@ def main():
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(
@@ -677,6 +749,10 @@ def main():
raise RuntimeError(
"You must specify at least one of --forwarder, "
"--auto-forwarders, or --no-forwarders options")
if 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])
@@ -719,6 +795,19 @@ def main():
# #######################################################################
if options.dns_over_tls and not CLIENT_SUPPORTS_NO_DNSSEC_VALIDATION:
ansible_module.fail_json(
msg="Important patches for DNS over TLS are missing in your "
"IPA version.")
client_dns_over_tls = self.dns_over_tls
if self.dns_over_tls and not self.setup_dns:
service.print_msg("Warning: --dns-over-tls option "
"specified without --setup-dns, ignoring")
client_dns_over_tls = False
# #######################################################################
# If any of the key file options are selected, all are required.
cert_file_req = (options.dirsrv_cert_files, options.http_cert_files)
cert_file_opt = (options.pkinit_cert_files,)
@@ -1208,6 +1297,7 @@ def main():
domainlevel=options.domainlevel,
sid_generation_always=sid_generation_always,
random_serial_numbers=options._random_serial_numbers,
client_dns_over_tls=client_dns_over_tls
)

View File

@@ -216,6 +216,14 @@ try:
except ImportError:
SerialNumber = None
try:
CLIENT_SUPPORTS_NO_DNSSEC_VALIDATION = False
from ipaclient.install.client import ClientInstallInterface
except ImportError:
pass
else:
if hasattr(ClientInstallInterface, "no_dnssec_validation"):
CLIENT_SUPPORTS_NO_DNSSEC_VALIDATION = True
else:
# IPA version < 4.5
raise RuntimeError("freeipa version '%s' is too old" % VERSION)
@@ -354,13 +362,6 @@ options.add_agents = False
# no_msdcs is deprecated
options.no_msdcs = False
# Hotfix for https://github.com/freeipa/freeipa/pull/7343
options.dns_over_tls = False
options.dns_over_tls_key = None
options.dns_over_tls_cert = None
options.dot_forwarders = None
options.dns_policy = None
# For pylint
options.external_cert_files = None
options.dirsrv_cert_files = None

View File

@@ -1,32 +1,42 @@
---
# tasks file for ipaserver
- name: Install - Set ipaserver__dns_over_lts
ansible.builtin.set_fact:
ipaserver__dns_over_tls: "{{ ipaserver_dns_over_tls | default(ipaclient_dns_over_tls) | default(False) }}"
- name: Install - Package installation
when: ipaserver_install_packages | bool
block:
- name: Install - Ensure that IPA server packages are installed
ansible.builtin.package:
name: "{{ ipaserver_packages }}"
state: present
- name: Install - Ensure that IPA server packages for dns are installed
ansible.builtin.package:
name: "{{ ipaserver_packages_dns }}"
state: present
- name: Install - Set packages for installation
ansible.builtin.set_fact:
_ipapackages: "{{ ipaserver_packages }}"
- name: Install - Set packages for installlation, add DNS
ansible.builtin.set_fact:
_ipapackages: "{{ _ipapackages + ipaserver_packages_dns }}"
when: ipaserver_setup_dns | bool
- name: Install - Ensure that IPA server packages for adtrust are installed
ansible.builtin.package:
name: "{{ ipaserver_packages_adtrust }}"
state: present
- name: Install - Set packages for installlation, add DOT
ansible.builtin.set_fact:
_ipapackages: "{{ _ipapackages + ipaserver_packages_dot }}"
when: ipaserver__dns_over_tls | bool
- name: Install - Set packages for installlation, add adtrust
ansible.builtin.set_fact:
_ipapackages: "{{ _ipapackages + ipaserver_packages_adtrust }}"
when: ipaserver_setup_adtrust | bool
- name: Install - Ensure that firewall packages installed
ansible.builtin.package:
name: "{{ ipaserver_packages_firewalld }}"
state: present
- name: Install - Set packages for installlation, add firewalld
ansible.builtin.set_fact:
_ipapackages: "{{ _ipapackages + ipaserver_packages_firewalld }}"
when: ipaserver_setup_firewalld | bool
- name: Install - Ensure that packages are installed
ansible.builtin.package:
name: "{{ _ipapackages }}"
state: present
- name: Install - Firewall configuration
when: ipaserver_setup_firewalld | bool
@@ -121,6 +131,11 @@
auto_forwarders: "{{ ipaserver_auto_forwarders }}"
forward_policy: "{{ ipaserver_forward_policy | default(omit) }}"
no_dnssec_validation: "{{ ipaserver_no_dnssec_validation }}"
dot_forwarders: "{{ ipaserver_dot_forwarders | default([]) }}"
dns_over_tls: "{{ ipaserver__dns_over_tls }}"
dns_over_tls_cert: "{{ ipaserver_dns_over_tls_cert | default(omit) }}"
dns_over_tls_key: "{{ ipaserver_dns_over_tls_key | default(omit) }}"
dns_policy: "{{ ipaserver_dns_policy | default(omit) }}"
### ad trust ###
enable_compat: "{{ ipaserver_enable_compat }}"
netbios_name: "{{ ipaserver_netbios_name | default(omit) }}"
@@ -192,6 +207,11 @@
auto_forwarders: "{{ ipaserver_auto_forwarders }}"
forward_policy: "{{ ipaserver_forward_policy | default(omit) }}"
no_dnssec_validation: "{{ ipaserver_no_dnssec_validation }}"
dot_forwarders: "{{ ipaserver_dot_forwarders | default([]) }}"
dns_over_tls: "{{ ipaserver__dns_over_tls }}"
dns_over_tls_cert: "{{ ipaserver_dns_over_tls_cert | default(omit) }}"
dns_over_tls_key: "{{ ipaserver_dns_over_tls_key | default(omit) }}"
dns_policy: "{{ ipaserver_dns_policy | default(omit) }}"
### ad trust ###
enable_compat: "{{ ipaserver_enable_compat }}"
netbios_name: "{{ ipaserver_netbios_name | default(omit) }}"
@@ -381,6 +401,11 @@
forward_policy: "{{ result_ipaserver_prepare.forward_policy }}"
zonemgr: "{{ ipaserver_zonemgr | default(omit) }}"
no_dnssec_validation: "{{ result_ipaserver_prepare.no_dnssec_validation }}"
dot_forwarders: "{{ ipaserver_dot_forwarders | default([]) }}"
dns_over_tls: "{{ ipaserver__dns_over_tls }}"
dns_over_tls_cert: "{{ ipaserver_dns_over_tls_cert | default(omit) }}"
dns_over_tls_key: "{{ ipaserver_dns_over_tls_key | default(omit) }}"
dns_policy: "{{ ipaserver_dns_policy | default(omit) }}"
### additional ###
dns_ip_addresses: "{{ result_ipaserver_prepare.dns_ip_addresses }}"
dns_reverse_zones: "{{ result_ipaserver_prepare.dns_reverse_zones }}"
@@ -432,6 +457,7 @@
ipaclient_no_ntp:
"{{ 'true' if result_ipaserver_test.ipa_python_version >= 40690
else 'false' }}"
ipaclient_dns_over_tls: "{{ result_ipaserver_test.client_dns_over_tls }}"
ipaclient_install_packages: no
- name: Install - Enable IPA
@@ -452,6 +478,8 @@
{{ "--add-service=freeipa-trust" if ipaserver_setup_adtrust | bool
else "" }}
{{ "--add-service=dns" if ipaserver_setup_dns | bool else "" }}
{{ "--add-service=dns-over-tls" if ipaserver__dns_over_tls | bool
else "" }}
{{ "--add-service=ntp" if not ipaclient_no_ntp | bool else "" }}
when: ipaserver_setup_firewalld | bool
@@ -465,6 +493,8 @@
{{ "--add-service=freeipa-trust" if ipaserver_setup_adtrust | bool
else "" }}
{{ "--add-service=dns" if ipaserver_setup_dns | bool else "" }}
{{ "--add-service=dns-over-tls" if ipaserver__dns_over_tls | bool
else "" }}
{{ "--add-service=ntp" if not ipaclient_no_ntp | bool else "" }}
when: ipaserver_setup_firewalld | bool

View File

@@ -3,5 +3,6 @@
---
ipaserver_packages: [ "freeipa-server", "python3-libselinux" ]
ipaserver_packages_dns: [ "freeipa-server-dns" ]
ipaserver_packages_dot: [ "freeipa-server-encrypted-dns" ]
ipaserver_packages_adtrust: [ "freeipa-server-trust-ad" ]
ipaserver_packages_firewalld: [ "firewalld" ]

View File

@@ -3,5 +3,6 @@
---
ipaserver_packages: [ "ipa-server", "libselinux-python" ]
ipaserver_packages_dns: [ "ipa-server-dns" ]
ipaserver_packages_dot: [ ]
ipaserver_packages_adtrust: [ "ipa-server-trust-ad" ]
ipaserver_packages_firewalld: [ "firewalld" ]

View File

@@ -3,5 +3,6 @@
---
ipaserver_packages: [ "@idm:DL1/server" ]
ipaserver_packages_dns: [ "@idm:DL1/dns" ]
ipaserver_packages_dot: [ ]
ipaserver_packages_adtrust: [ "@idm:DL1/adtrust" ]
ipaserver_packages_firewalld: [ "firewalld" ]

View File

@@ -2,6 +2,7 @@
---
ipaserver_packages: [ "freeipa-server" ]
ipaserver_packages_dns: [ "freeipa-server-dns" ]
ipaserver_packages_dot: [ ]
ipaserver_packages_adtrust: [ "freeipa-server-trust-ad" ]
ipaserver_packages_firewalld: [ "firewalld" ]
# Ubuntu Bionic Beaver must use python2 as Python interpreter due

View File

@@ -3,5 +3,6 @@
---
ipaserver_packages: [ "freeipa-server" ]
ipaserver_packages_dns: [ "freeipa-server-dns" ]
ipaserver_packages_dot: [ ]
ipaserver_packages_adtrust: [ "freeipa-server-trust-ad" ]
ipaserver_packages_firewalld: [ "firewalld" ]

View File

@@ -3,5 +3,6 @@
---
ipaserver_packages: [ "ipa-server", "python3-libselinux" ]
ipaserver_packages_dns: [ "ipa-server-dns" ]
ipaserver_packages_dot: [ "ipa-server-encrypted-dns" ]
ipaserver_packages_adtrust: [ "freeipa-server-trust-ad" ]
ipaserver_packages_firewalld: [ "firewalld" ]