From 7a23c668fc9cf08a29e27246e2ab4957b530ed53 Mon Sep 17 00:00:00 2001 From: Thomas Woerner Date: Tue, 1 Jul 2025 14:19:13 +0200 Subject: [PATCH 1/3] ipaclient: Add support for DNS over TLS This change adds support for DNS over TLS to the ipaclient role. New variables ipaclient_dns_over_tls Configure DNS over TLS. Requires FreeIPA version 4.12.5 or later. (bool, default: false) required: false ipaclient_no_dnssec_validation Disable DNSSEC validation for DNS over TLS. This turns off DNSSEC validation for unbound. Only usable if `ipaserver_dns_over_tls` is enabled. (bool, default: false) reqiured: false New distribution specific variable ipaclient_packages_dot List of IPA packages needed for DNS over TLS. The resolver configuratoin for DNS over TLS is not part of this change and will be added later on. Therefore it is needed to configure the resolver for DNS over TLS before starting the deployment with ipaclient role. This is essential for using an IPA DNS server with DoT and enforced DNS policy so that only DoT is usable. --- roles/ipaclient/README.md | 2 ++ roles/ipaclient/defaults/main.yml | 2 ++ .../ipaclient/library/ipaclient_setup_nss.py | 19 +++++++++++-- .../ipaclient/library/ipaclient_setup_sssd.py | 7 +++++ roles/ipaclient/library/ipaclient_test.py | 28 ++++++++++++++++++- .../module_utils/ansible_ipa_client.py | 9 ++++++ roles/ipaclient/tasks/install.yml | 27 ++++++++++++++---- roles/ipaclient/vars/Debian-10.yml | 1 + roles/ipaclient/vars/Debian.yml | 1 + roles/ipaclient/vars/RedHat-7.yml | 1 + roles/ipaclient/vars/RedHat-8.yml | 1 + roles/ipaclient/vars/Ubuntu-18.04.yml | 1 + roles/ipaclient/vars/default.yml | 1 + 13 files changed, 92 insertions(+), 8 deletions(-) diff --git a/roles/ipaclient/README.md b/roles/ipaclient/README.md index 726856c7..3c09d129 100644 --- a/roles/ipaclient/README.md +++ b/roles/ipaclient/README.md @@ -202,6 +202,8 @@ Variable | Description | Required `ipaclient_request_cert` | The bool value defines if the certificate for the machine wil be requested. The certificate will be stored in /etc/ipa/nssdb under the nickname "Local IPA host". . `ipaclient_request_cert` defaults to `no`. The option is deprecated and will be removed in a future release. | no `ipaclient_keytab` | The string value contains the path on the node of a backup host keytab from a previous enrollment. | no `ipaclient_automount_location` | Automount location | no +`ipaclient_dns_over_tls` | Configure DNS over TLS. Requires FreeIPA version 4.12.5 or later. (bool, default: false) | no +`ipaclient_no_dnssec_validation` | Disable DNSSEC validation for DNS over TLS. This turns off DNSSEC validation for unbound. Ignored if `ipaserver_dns_over_tls` is not enabled. (bool, default: false) | no Server Variables diff --git a/roles/ipaclient/defaults/main.yml b/roles/ipaclient/defaults/main.yml index b6e912f2..ab8f3f02 100644 --- a/roles/ipaclient/defaults/main.yml +++ b/roles/ipaclient/defaults/main.yml @@ -26,6 +26,8 @@ ipasssd_enable_dns_updates: no ipasssd_no_krb5_offline_passwords: no ipasssd_preserve_sssd: no ipaclient_request_cert: no +ipaclient_dns_over_tls: no +ipaclient_no_dnssec_validation: no ### packages ### ipaclient_install_packages: yes diff --git a/roles/ipaclient/library/ipaclient_setup_nss.py b/roles/ipaclient/library/ipaclient_setup_nss.py index d9fdda68..6ab4186d 100644 --- a/roles/ipaclient/library/ipaclient_setup_nss.py +++ b/roles/ipaclient/library/ipaclient_setup_nss.py @@ -86,6 +86,16 @@ options: type: bool required: no default: no + dns_over_tls: + description: Configure DNS over TLS + type: bool + default: no + required: no + no_dnssec_validation: + description: Disable DNSSEC validation for DNS over TLS + type: bool + default: no + required: no enable_dns_updates: description: | Configures the machine to attempt dns updates when the ip address @@ -212,7 +222,9 @@ def main(): mkhomedir=dict(required=False, type='bool'), on_master=dict(required=False, type='bool'), dnsok=dict(required=False, type='bool', default=False), - + dns_over_tls=dict(required=False, type='bool', default=False), + no_dnssec_validation=dict(required=False, type='bool', + default=False), enable_dns_updates=dict(required=False, type='bool'), all_ip_addresses=dict(required=False, type='bool', default=False), ip_addresses=dict(required=False, type='list', elements='str', @@ -249,6 +261,8 @@ def main(): options.mkhomedir = module.params.get('mkhomedir') options.on_master = module.params.get('on_master') dnsok = module.params.get('dnsok') + options.dns_over_tls = module.params.get('dns_over_tls') + options.no_dnssec_validation = module.params.get('no_dnssec_validation') fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE) statestore = sysrestore.StateFile(paths.IPA_CLIENT_SYSRESTORE) @@ -256,6 +270,7 @@ def main(): os.environ['KRB5CCNAME'] = paths.IPA_DNS_CCACHE options.dns_updates = module.params.get('enable_dns_updates') + options.dns_over_tls = module.params.get('dns_over_tls') options.all_ip_addresses = module.params.get('all_ip_addresses') options.ip_addresses = ansible_module_get_parsed_ip_addresses(module) options.request_cert = module.params.get('request_cert') @@ -279,7 +294,7 @@ def main(): options.no_sssd = False options.sssd = not options.no_sssd options.no_ac = False - options.dns_over_tls = False + options.dns_over_tls = module.params.get('dns_over_tls') nosssd_files = module.params.get('nosssd_files') selinux_works = module.params.get('selinux_works') krb_name = module.params.get('krb_name') diff --git a/roles/ipaclient/library/ipaclient_setup_sssd.py b/roles/ipaclient/library/ipaclient_setup_sssd.py index 434ec73a..d12b71f4 100644 --- a/roles/ipaclient/library/ipaclient_setup_sssd.py +++ b/roles/ipaclient/library/ipaclient_setup_sssd.py @@ -91,6 +91,11 @@ options: changes type: bool required: no + dns_over_tls: + description: Configure DNS over TLS + type: bool + default: no + required: no preserve_sssd: description: Preserve old SSSD configuration if possible type: bool @@ -140,6 +145,7 @@ def main(): fixed_primary=dict(required=False, type='bool'), permit=dict(required=False, type='bool'), enable_dns_updates=dict(required=False, type='bool'), + dns_over_tls=dict(required=False, type='bool', default=False), preserve_sssd=dict(required=False, type='bool'), no_krb5_offline_passwords=dict(required=False, type='bool'), ), @@ -169,6 +175,7 @@ def main(): options.primary = module.params.get('fixed_primary') options.permit = module.params.get('permit') options.dns_updates = module.params.get('enable_dns_updates') + options.dns_over_tls = module.params.get('dns_over_tls') options.preserve_sssd = module.params.get('preserve_sssd') options.no_krb5_offline_passwords = module.params.get( diff --git a/roles/ipaclient/library/ipaclient_test.py b/roles/ipaclient/library/ipaclient_test.py index 75970c58..b90ae943 100644 --- a/roles/ipaclient/library/ipaclient_test.py +++ b/roles/ipaclient/library/ipaclient_test.py @@ -124,6 +124,16 @@ options: type: bool required: no default: no + dns_over_tls: + description: Configure DNS over TLS + type: bool + default: no + required: no + no_dnssec_validation: + description: Disable DNSSEC validation for DNS over TLS + type: bool + default: no + required: no enable_dns_updates: description: Configures the machine to attempt dns updates when the ip address @@ -248,7 +258,8 @@ from ansible.module_utils.ansible_ipa_client import ( CLIENT_INSTALL_ERROR, tasks, check_ldap_conf, timeconf, constants, validate_hostname, nssldap_exists, gssapi, remove_file, check_ip_addresses, ipadiscovery, print_port_conf_info, - IPA_PYTHON_VERSION, getargspec + IPA_PYTHON_VERSION, getargspec, services, + CLIENT_SUPPORTS_NO_DNSSEC_VALIDATION ) @@ -328,6 +339,9 @@ def main(): default=None), all_ip_addresses=dict(required=False, type='bool', default=False), on_master=dict(required=False, type='bool', default=False), + dns_over_tls=dict(required=False, type='bool', default=False), + no_dnssec_validation=dict(required=False, type='bool', + default=False), # sssd enable_dns_updates=dict(required=False, type='bool', default=False), @@ -356,6 +370,8 @@ def main(): options.ip_addresses = module.params.get('ip_addresses') options.all_ip_addresses = module.params.get('all_ip_addresses') options.on_master = module.params.get('on_master') + options.dns_over_tls = module.params.get('dns_over_tls') + options.no_dnssec_validation = module.params.get('no_dnssec_validation') options.enable_dns_updates = module.params.get('enable_dns_updates') # Get domain from first server if domain is not set, but if there are @@ -365,6 +381,16 @@ def main(): options.domain_name = options.servers[0][ options.servers[0].find(".") + 1:] + if options.dns_over_tls \ + and not services.knownservices["unbound"].is_installed(): + module.fail_json( + msg="To enable DNS over TLS, package ipa-client-encrypted-dns " + "must be installed.") + if options.dns_over_tls and not CLIENT_SUPPORTS_NO_DNSSEC_VALIDATION: + module.fail_json( + msg="Important patches for DNS over TLS are missing in your IPA " + "version.") + try: self = options diff --git a/roles/ipaclient/module_utils/ansible_ipa_client.py b/roles/ipaclient/module_utils/ansible_ipa_client.py index 1a3a974e..08ced240 100644 --- a/roles/ipaclient/module_utils/ansible_ipa_client.py +++ b/roles/ipaclient/module_utils/ansible_ipa_client.py @@ -310,6 +310,15 @@ try: except ImportError: configure_selinux_for_client = 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 + logger = logging.getLogger("ipa-client-install") root_logger = logger diff --git a/roles/ipaclient/tasks/install.yml b/roles/ipaclient/tasks/install.yml index ae9b0669..ca56be3d 100644 --- a/roles/ipaclient/tasks/install.yml +++ b/roles/ipaclient/tasks/install.yml @@ -1,11 +1,23 @@ --- # tasks file for ipaclient -- name: Install - Ensure that IPA client packages are installed - ansible.builtin.package: - name: "{{ ipaclient_packages }}" - state: present +- name: Install - Package installation when: ipaclient_install_packages | bool + block: + + - name: Install - Set packages for installation + ansible.builtin.set_fact: + _ipapackages: "{{ ipaclient_packages }}" + + - name: Install - Set packages for installlation, add DOT + ansible.builtin.set_fact: + _ipapackages: "{{ _ipapackages + ipaclient_packages_dot }}" + when: ipaclient_dns_over_tls | bool + + - name: Install - Ensure that packages are installed + ansible.builtin.package: + name: "{{ _ipapackages }}" + state: present - name: Install - Set ipaclient_servers ansible.builtin.set_fact: @@ -38,7 +50,7 @@ msg: "ipaclient_domain or ipaserver_domain is required for ipaclient_configure_dns_resolver" when: ipaserver_domain is not defined and ipaclient_domain is not defined - - name: Install - Fail on missing ipaclient_servers + - name: Install - Fail on missing ipaclient_dns_servers ansible.builtin.fail: msg: "ipaclient_dns_servers is required for ipaclient_configure_dns_resolver" when: ipaclient_dns_servers is not defined @@ -69,6 +81,8 @@ ip_addresses: "{{ ipaclient_ip_addresses | default(omit) }}" all_ip_addresses: "{{ ipaclient_all_ip_addresses }}" on_master: "{{ ipaclient_on_master }}" + dns_over_tls: "{{ ipaclient_dns_over_tls }}" + no_dnssec_validation: "{{ ipaclient_no_dnssec_validation }}" ### sssd ### enable_dns_updates: "{{ ipasssd_enable_dns_updates }}" register: result_ipaclient_test @@ -323,6 +337,7 @@ fixed_primary: "{{ ipasssd_fixed_primary }}" permit: "{{ ipasssd_permit }}" enable_dns_updates: "{{ ipasssd_enable_dns_updates }}" + dns_over_tls: "{{ ipaclient_dns_over_tls }}" preserve_sssd: "{{ ipasssd_preserve_sssd }}" no_krb5_offline_passwords: "{{ ipasssd_no_krb5_offline_passwords }}" @@ -360,6 +375,8 @@ on_master: "{{ ipaclient_on_master }}" dnsok: "{{ result_ipaclient_test.dnsok }}" enable_dns_updates: "{{ ipasssd_enable_dns_updates }}" + dns_over_tls: "{{ ipaclient_dns_over_tls }}" + no_dnssec_validation: "{{ ipaclient_no_dnssec_validation }}" all_ip_addresses: "{{ ipaclient_all_ip_addresses }}" ip_addresses: "{{ ipaclient_ip_addresses | default(omit) }}" request_cert: "{{ ipaclient_request_cert }}" diff --git a/roles/ipaclient/vars/Debian-10.yml b/roles/ipaclient/vars/Debian-10.yml index 8de5dc8e..2beaa8b7 100644 --- a/roles/ipaclient/vars/Debian-10.yml +++ b/roles/ipaclient/vars/Debian-10.yml @@ -1,6 +1,7 @@ --- # vars/Debian.yml ipaclient_packages: [ "freeipa-client" ] +ipaclient_packages_dot: [ ] # Debian Buster must use python2 as Python interpreter due # to the way freeipa-client package is defined. # You must install package python2.7 before executing this role. diff --git a/roles/ipaclient/vars/Debian.yml b/roles/ipaclient/vars/Debian.yml index efa7c4d4..73fc7909 100644 --- a/roles/ipaclient/vars/Debian.yml +++ b/roles/ipaclient/vars/Debian.yml @@ -2,3 +2,4 @@ # vars/Debian.yml --- ipaclient_packages: [ "freeipa-client" ] +ipaclient_packages_dot: [ ] diff --git a/roles/ipaclient/vars/RedHat-7.yml b/roles/ipaclient/vars/RedHat-7.yml index c154c99f..3e7f75b6 100644 --- a/roles/ipaclient/vars/RedHat-7.yml +++ b/roles/ipaclient/vars/RedHat-7.yml @@ -2,3 +2,4 @@ # vars/RedHat-7 --- ipaclient_packages: [ "ipa-client", "libselinux-python" ] +ipaclient_packages_dot: [ ] diff --git a/roles/ipaclient/vars/RedHat-8.yml b/roles/ipaclient/vars/RedHat-8.yml index 65b554e0..f663c694 100644 --- a/roles/ipaclient/vars/RedHat-8.yml +++ b/roles/ipaclient/vars/RedHat-8.yml @@ -2,3 +2,4 @@ # vars/RedHat-8.yml --- ipaclient_packages: [ "@idm:DL1/client" ] +ipaclient_packages_dot: [ ] diff --git a/roles/ipaclient/vars/Ubuntu-18.04.yml b/roles/ipaclient/vars/Ubuntu-18.04.yml index e01f5021..44a638ca 100644 --- a/roles/ipaclient/vars/Ubuntu-18.04.yml +++ b/roles/ipaclient/vars/Ubuntu-18.04.yml @@ -1,6 +1,7 @@ # vars/Ubuntu-18.04.yml --- ipaclient_packages: [ "freeipa-client" ] +ipaclient_packages_dot: [ ] # Ubuntu Bionic Beaver must use python2 as Python interpreter due # to the way python-ipalib package is defined. # Package python2.7 must be installed before executing this role. diff --git a/roles/ipaclient/vars/default.yml b/roles/ipaclient/vars/default.yml index 2cfb3614..2b2ce88f 100644 --- a/roles/ipaclient/vars/default.yml +++ b/roles/ipaclient/vars/default.yml @@ -2,3 +2,4 @@ # vars/default.yml --- ipaclient_packages: [ "ipa-client", "python3-libselinux" ] +ipaclient_packages_dot: [ "ipa-client-encrypted-dns" ] From e2317f304cf3f93eafec33aacad2cf86f4b5581a Mon Sep 17 00:00:00 2001 From: Thomas Woerner Date: Tue, 1 Jul 2025 14:31:51 +0200 Subject: [PATCH 2/3] 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. --- roles/ipaserver/README.md | 6 ++ roles/ipaserver/library/ipaserver_prepare.py | 40 ++++++++ .../ipaserver/library/ipaserver_setup_dns.py | 62 +++++++++--- roles/ipaserver/library/ipaserver_test.py | 94 ++++++++++++++++++- .../module_utils/ansible_ipa_server.py | 15 +-- roles/ipaserver/tasks/install.yml | 62 ++++++++---- roles/ipaserver/vars/Fedora.yml | 1 + roles/ipaserver/vars/RedHat-7.yml | 1 + roles/ipaserver/vars/RedHat-8.yml | 1 + roles/ipaserver/vars/Ubuntu-18.04.yml | 1 + roles/ipaserver/vars/Ubuntu.yml | 1 + roles/ipaserver/vars/default.yml | 1 + 12 files changed, 246 insertions(+), 39 deletions(-) diff --git a/roles/ipaserver/README.md b/roles/ipaserver/README.md index a055e4a7..3ac9e35b 100644 --- a/roles/ipaserver/README.md +++ b/roles/ipaserver/README.md @@ -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 ------------------ diff --git a/roles/ipaserver/library/ipaserver_prepare.py b/roles/ipaserver/library/ipaserver_prepare.py index 1276729f..ff11863c 100644 --- a/roles/ipaserver/library/ipaserver_prepare.py +++ b/roles/ipaserver/library/ipaserver_prepare.py @@ -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') diff --git a/roles/ipaserver/library/ipaserver_setup_dns.py b/roles/ipaserver/library/ipaserver_setup_dns.py index b06246c4..6ab0a68b 100644 --- a/roles/ipaserver/library/ipaserver_setup_dns.py +++ b/roles/ipaserver/library/ipaserver_setup_dns.py @@ -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 ################################################################## diff --git a/roles/ipaserver/library/ipaserver_test.py b/roles/ipaserver/library/ipaserver_test.py index 06df1b70..e8610167 100644 --- a/roles/ipaserver/library/ipaserver_test.py +++ b/roles/ipaserver/library/ipaserver_test.py @@ -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 ) diff --git a/roles/ipaserver/module_utils/ansible_ipa_server.py b/roles/ipaserver/module_utils/ansible_ipa_server.py index d2a1fbc0..0b2e8623 100644 --- a/roles/ipaserver/module_utils/ansible_ipa_server.py +++ b/roles/ipaserver/module_utils/ansible_ipa_server.py @@ -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 diff --git a/roles/ipaserver/tasks/install.yml b/roles/ipaserver/tasks/install.yml index 1a85f7f4..f7ad7472 100644 --- a/roles/ipaserver/tasks/install.yml +++ b/roles/ipaserver/tasks/install.yml @@ -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 diff --git a/roles/ipaserver/vars/Fedora.yml b/roles/ipaserver/vars/Fedora.yml index 14701340..d5043c54 100644 --- a/roles/ipaserver/vars/Fedora.yml +++ b/roles/ipaserver/vars/Fedora.yml @@ -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" ] diff --git a/roles/ipaserver/vars/RedHat-7.yml b/roles/ipaserver/vars/RedHat-7.yml index 3c524898..06c7598b 100644 --- a/roles/ipaserver/vars/RedHat-7.yml +++ b/roles/ipaserver/vars/RedHat-7.yml @@ -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" ] diff --git a/roles/ipaserver/vars/RedHat-8.yml b/roles/ipaserver/vars/RedHat-8.yml index 9efe0cff..06ff3194 100644 --- a/roles/ipaserver/vars/RedHat-8.yml +++ b/roles/ipaserver/vars/RedHat-8.yml @@ -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" ] diff --git a/roles/ipaserver/vars/Ubuntu-18.04.yml b/roles/ipaserver/vars/Ubuntu-18.04.yml index 325d86a5..8869f0ee 100644 --- a/roles/ipaserver/vars/Ubuntu-18.04.yml +++ b/roles/ipaserver/vars/Ubuntu-18.04.yml @@ -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 diff --git a/roles/ipaserver/vars/Ubuntu.yml b/roles/ipaserver/vars/Ubuntu.yml index 2d58f471..e5f94e1a 100644 --- a/roles/ipaserver/vars/Ubuntu.yml +++ b/roles/ipaserver/vars/Ubuntu.yml @@ -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" ] diff --git a/roles/ipaserver/vars/default.yml b/roles/ipaserver/vars/default.yml index 395bb075..e128fa77 100644 --- a/roles/ipaserver/vars/default.yml +++ b/roles/ipaserver/vars/default.yml @@ -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" ] From cd440a20498115ca111e9c254496caeeffa79499 Mon Sep 17 00:00:00 2001 From: Thomas Woerner Date: Wed, 2 Jul 2025 09:35:00 +0200 Subject: [PATCH 3/3] ipareplica: Add support for DNS over TLS This change adds support for DNS over TLS to the ipareplica role. New variables ipareplica_dot_forwarders List of DNS over TLS forwarders. Required if ipareplica_dns_over_tls is enabled. (list of strings) required: false ipareplica_dns_over_tls | ipaclient_dns_over_tls Configure DNS over TLS. Requires FreeIPA version 4.12.5 or later. (bool, default: false) required: false ipareplica_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 ipareplica_dns_over_tls_key Key for certificate specified in ipareplica_dns_over_tls_cert. (string) required: false ipareplica_dns_policy Encrypted DNS policy. Only usable if `ipareplica_dns_over_tls` is enabled. (choice: relaxed, enforced, default: relaxed) required: false New distribution specific variable ipareplica_packages_dot List of IPA packages needed for DNS over TLS. --- roles/ipareplica/README.md | 5 ++ .../ipareplica/library/ipareplica_prepare.py | 40 +++++++++ .../library/ipareplica_setup_dns.py | 42 ++++++++++ roles/ipareplica/library/ipareplica_test.py | 84 ++++++++++++++++++- .../module_utils/ansible_ipa_replica.py | 14 ++-- roles/ipareplica/tasks/install.yml | 65 ++++++++++---- roles/ipareplica/vars/Fedora.yml | 1 + roles/ipareplica/vars/RedHat-7.yml | 1 + roles/ipareplica/vars/RedHat-8.yml | 1 + roles/ipareplica/vars/Ubuntu-18.04.yml | 1 + roles/ipareplica/vars/Ubuntu.yml | 1 + roles/ipareplica/vars/default.yml | 7 +- 12 files changed, 235 insertions(+), 27 deletions(-) diff --git a/roles/ipareplica/README.md b/roles/ipareplica/README.md index 21371330..93f69b73 100644 --- a/roles/ipareplica/README.md +++ b/roles/ipareplica/README.md @@ -270,6 +270,11 @@ Variable | Description | Required `ipareplica_auto_forwarders` | Add DNS forwarders configured in /etc/resolv.conf to the list of forwarders used by IPA DNS. (bool, default: false) | no `ipareplica_forward_policy` | DNS forwarding policy for global forwarders specified using other options. (choice: first,only) | no `ipareplica_no_dnssec_validation` | Disable DNSSEC validation on this server. (bool, default: false) | no +`ipareplica_dot_forwarders` | List of DNS over TLS forwarders. Required if `ipareplica_dns_over_tls` is enabled. (list of strings) | no +`ipareplica_dns_over_tls` \| `ipaclient_dns_over_tls` | Configure DNS over TLS. Requires FreeIPA version 4.12.5 or later. (bool, default: false) | no +`ipareplica_dns_over_tls_cert` | Certificate to use for DNS over TLS. If empty, a new certificate will be requested from IPA CA. (string) | no +`ipareplica_dns_over_tls_key` | Key for certificate specified in `ipareplica_dns_over_tls_cert`. (string) | no +`ipareplica_dns_policy` | Encrypted DNS policy. Only usable if `ipareplica_dns_over_tls` is enabled. (choice: relaxed, enforced, default: relaxed) | no AD trust Variables ------------------ diff --git a/roles/ipareplica/library/ipareplica_prepare.py b/roles/ipareplica/library/ipareplica_prepare.py index 74965946..5edbba5d 100644 --- a/roles/ipareplica/library/ipareplica_prepare.py +++ b/roles/ipareplica/library/ipareplica_prepare.py @@ -224,6 +224,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 @@ -354,6 +380,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'), @@ -430,6 +465,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') diff --git a/roles/ipareplica/library/ipareplica_setup_dns.py b/roles/ipareplica/library/ipareplica_setup_dns.py index d8c643e5..ef8e93e9 100644 --- a/roles/ipareplica/library/ipareplica_setup_dns.py +++ b/roles/ipareplica/library/ipareplica_setup_dns.py @@ -72,6 +72,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 @@ -117,6 +143,9 @@ from ansible.module_utils.ansible_ipa_replica import ( gen_ReplicaConfig, gen_remote_api, api, redirect_stdout, dns, ansible_module_get_parsed_ip_addresses ) +# pylint: disable=unused-import +from ansible.module_utils.ansible_ipa_replica import bindinstance # noqa: F401 +# pylint: enable=unused-import def main(): @@ -135,6 +164,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'), # additional dns_ip_addresses=dict(required=True, type='list', elements='str'), dns_reverse_zones=dict(required=True, type='list', elements='str'), @@ -167,6 +204,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') diff --git a/roles/ipareplica/library/ipareplica_test.py b/roles/ipareplica/library/ipareplica_test.py index 06ecad4e..94b41e58 100644 --- a/roles/ipareplica/library/ipareplica_test.py +++ b/roles/ipareplica/library/ipareplica_test.py @@ -181,6 +181,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' author: - Thomas Woerner (@t-woerner) ''' @@ -199,7 +225,8 @@ from ansible.module_utils.ansible_ipa_replica import ( paths, sysrestore, ansible_module_get_parsed_ip_addresses, service, redirect_stdout, create_ipa_conf, ipautil, x509, validate_domain_name, common_check, - IPA_PYTHON_VERSION, getargspec, adtrustinstance, install_ca_cert + IPA_PYTHON_VERSION, getargspec, adtrustinstance, install_ca_cert, + services, CLIENT_SUPPORTS_NO_DNSSEC_VALIDATION ) @@ -250,6 +277,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'), ), ) @@ -298,6 +333,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') ########################################################################## # replica init ########################################################### @@ -419,6 +459,14 @@ def main(): ansible_module.fail_json( msg="You cannot specify a --no-dnssec-validation option " "without the --setup-dns option") + if installer.dns_over_tls_cert: + ansible_module.fail_json( + msg="You cannot specify a --dns-over-tls-cert option " + "without the --setup-dns option") + if installer.dns_over_tls_key: + ansible_module.fail_json( + msg="You cannot specify a --dns-over-tls-key option " + "without the --setup-dns option") elif installer.forwarders and installer.no_forwarders: ansible_module.fail_json( msg="You cannot specify a --forwarder option together with " @@ -435,6 +483,31 @@ def main(): ansible_module.fail_json( msg="You cannot specify a --auto-reverse option together with " "--no-reverse") + elif installer.dot_forwarders and not installer.dns_over_tls: + ansible_module.fail_json( + msg="You cannot specify a --dot-forwarder option " + "without the --dns-over-tls option") + elif (installer.dns_over_tls + and not services.knownservices["unbound"].is_installed()): + ansible_module.fail_json( + msg="To enable DNS over TLS, package ipa-server-encrypted-dns " + "must be installed.") + elif installer.dns_policy == "enforced" and not installer.dns_over_tls: + ansible_module.fail_json( + msg="You cannot specify a --dns-policy option " + "without the --dns-over-tls option") + elif installer.dns_over_tls_cert and not installer.dns_over_tls: + ansible_module.fail_json( + msg="You cannot specify a --dns-over-tls-cert option " + "without the --dns-over-tls option") + elif installer.dns_over_tls_key and not installer.dns_over_tls: + ansible_module.fail_json( + msg="You cannot specify a --dns-over-tls-key option " + "without the --dns-over-tls option") + elif bool(installer.dns_over_tls_key) != bool(installer.dns_over_tls_cert): + ansible_module.fail_json( + msg="You cannot specify a --dns-over-tls-key option " + "without the --dns-over-tls-cert option and vice versa") # replica installers if installer.servers and not installer.domain_name: @@ -449,6 +522,10 @@ def main(): ansible_module.fail_json( msg="You must specify at least one of --forwarder, " "--auto-forwarders, or --no-forwarders options") + if installer.dns_over_tls and not installer.dot_forwarders: + ansible_module.fail_json( + msg="You must specify --dot-forwarder " + "when enabling DNS over TLS") if installer.dirsrv_config_file is not None and \ not os.path.exists(installer.dirsrv_config_file): @@ -486,6 +563,11 @@ def main(): if installer.domain_name is not None: validate_domain_name(installer.domain_name) + if installer.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.") + ########################################################################## # replica promote_check excerpts ######################################### ########################################################################## diff --git a/roles/ipareplica/module_utils/ansible_ipa_replica.py b/roles/ipareplica/module_utils/ansible_ipa_replica.py index 06aa71d4..6b5c4781 100644 --- a/roles/ipareplica/module_utils/ansible_ipa_replica.py +++ b/roles/ipareplica/module_utils/ansible_ipa_replica.py @@ -187,6 +187,14 @@ try: from ipaserver.install import ntpinstance time_service = "ntpd" # pylint: disable=invalid-name + 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.6 raise RuntimeError("freeipa version '%s' is too old" % VERSION) @@ -337,12 +345,6 @@ options.add_agents = False options.subject_base = None options.ca_subject = None -# 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 # pylint: enable=attribute-defined-outside-init diff --git a/roles/ipareplica/tasks/install.yml b/roles/ipareplica/tasks/install.yml index 4bee1c7b..76cffffd 100644 --- a/roles/ipareplica/tasks/install.yml +++ b/roles/ipareplica/tasks/install.yml @@ -1,33 +1,43 @@ --- # tasks file for ipareplica -- name: Package installation +- name: Install - Set ipareplica__dns_over_lts + ansible.builtin.set_fact: + ipareplica__dns_over_tls: "{{ ipareplica_dns_over_tls | default(ipaclient_dns_over_tls) | default(False) }}" + +- name: Install - Package installation when: ipareplica_install_packages | bool block: - - name: Install - Ensure IPA replica packages are installed - ansible.builtin.package: - name: "{{ ipareplica_packages }}" - state: present + - name: Install - Set packages for installation + ansible.builtin.set_fact: + _ipapackages: "{{ ipareplica_packages }}" - - name: Install - Ensure IPA replica packages for dns are installed - ansible.builtin.package: - name: "{{ ipareplica_packages_dns }}" - state: present + - name: Install - Set packages for installlation, add DNS + ansible.builtin.set_fact: + _ipapackages: "{{ _ipapackages + ipareplica_packages_dns }}" when: ipareplica_setup_dns | bool - - name: Install - Ensure IPA replica packages for adtrust are installed - ansible.builtin.package: - name: "{{ ipareplica_packages_adtrust }}" - state: present + - name: Install - Set packages for installlation, add DOT + ansible.builtin.set_fact: + _ipapackages: "{{ _ipapackages + ipareplica_packages_dot }}" + when: ipareplica__dns_over_tls | bool + + - name: Install - Set packages for installlation, add adtrust + ansible.builtin.set_fact: + _ipapackages: "{{ _ipapackages + ipareplica_packages_adtrust }}" when: ipareplica_setup_adtrust | bool - - name: Install - Ensure that firewall packages installed - ansible.builtin.package: - name: "{{ ipareplica_packages_firewalld }}" - state: present + - name: Install - Set packages for installlation, add firewalld + ansible.builtin.set_fact: + _ipapackages: "{{ _ipapackages + ipareplica_packages_firewalld }}" when: ipareplica_setup_firewalld | bool + - name: Install - Ensure that packages are installed + ansible.builtin.package: + name: "{{ _ipapackages }}" + state: present + - name: Firewall configuration when: ipareplica_setup_firewalld | bool block: @@ -104,6 +114,11 @@ auto_forwarders: "{{ ipareplica_auto_forwarders }}" forward_policy: "{{ ipareplica_forward_policy | default(omit) }}" no_dnssec_validation: "{{ ipareplica_no_dnssec_validation }}" + dot_forwarders: "{{ ipareplica_dot_forwarders | default([]) }}" + dns_over_tls: "{{ ipareplica__dns_over_tls }}" + dns_over_tls_cert: "{{ ipareplica_dns_over_tls_cert | default(omit) }}" + dns_over_tls_key: "{{ ipareplica_dns_over_tls_key | default(omit) }}" + dns_policy: "{{ ipareplica_dns_policy | default(omit) }}" register: result_ipareplica_test - name: Install - Deploy replica @@ -127,6 +142,8 @@ ipaclient_hostname: "{{ result_ipareplica_test.hostname }}" ipaclient_ip_addresses: "{{ ipareplica_ip_addresses | default(omit) }}" ipaclient_install_packages: "{{ ipareplica_install_packages }}" + ipaclient_dns_over_tls: "{{ ipareplica__dns_over_tls }}" + ipaclient_no_dnssec_validation: "{{ ipareplica_no_dnssec_validation }}" when: not result_ipareplica_test.client_enrolled - name: Install - Configure firewalld @@ -140,6 +157,8 @@ {{ "--add-service=freeipa-trust" if result_ipareplica_test.setup_adtrust else "" }} {{ "--add-service=dns" if ipareplica_setup_dns | bool else "" }} + {{ "--add-service=dns-over-tls" if ipareplica__dns_over_tls | bool + else "" }} {{ "--add-service=ntp" if not ipaclient_no_ntp | bool else "" }} when: ipareplica_setup_firewalld | bool @@ -153,6 +172,8 @@ {{ "--add-service=freeipa-trust" if result_ipareplica_test.setup_adtrust else "" }} {{ "--add-service=dns" if ipareplica_setup_dns | bool else "" }} + {{ "--add-service=dns-over-tls" if ipareplica__dns_over_tls | bool + else "" }} {{ "--add-service=ntp" if not ipaclient_no_ntp | bool else "" }} when: ipareplica_setup_firewalld | bool @@ -201,6 +222,11 @@ auto_forwarders: "{{ ipareplica_auto_forwarders }}" forward_policy: "{{ ipareplica_forward_policy | default(omit) }}" no_dnssec_validation: "{{ ipareplica_no_dnssec_validation }}" + dot_forwarders: "{{ ipareplica_dot_forwarders | default([]) }}" + dns_over_tls: "{{ ipareplica__dns_over_tls }}" + dns_over_tls_cert: "{{ ipareplica_dns_over_tls_cert | default(omit) }}" + dns_over_tls_key: "{{ ipareplica_dns_over_tls_key | default(omit) }}" + dns_policy: "{{ ipareplica_dns_policy | default(omit) }}" ### ad trust ### enable_compat: "{{ ipareplica_enable_compat }}" netbios_name: "{{ ipareplica_netbios_name | default(omit) }}" @@ -717,6 +743,11 @@ result_ipareplica_prepare.forward_policy is not none else omit }}" no_dnssec_validation: "{{ ipareplica_no_dnssec_validation }}" + dot_forwarders: "{{ ipareplica_dot_forwarders | default([]) }}" + dns_over_tls: "{{ ipareplica__dns_over_tls }}" + dns_over_tls_cert: "{{ ipareplica_dns_over_tls_cert | default(omit) }}" + dns_over_tls_key: "{{ ipareplica_dns_over_tls_key | default(omit) }}" + dns_policy: "{{ ipareplica_dns_policy | default(omit) }}" ### additional ### dns_ip_addresses: "{{ result_ipareplica_prepare.dns_ip_addresses }}" dns_reverse_zones: "{{ result_ipareplica_prepare.dns_reverse_zones }}" diff --git a/roles/ipareplica/vars/Fedora.yml b/roles/ipareplica/vars/Fedora.yml index ffbdaeec..025910a2 100644 --- a/roles/ipareplica/vars/Fedora.yml +++ b/roles/ipareplica/vars/Fedora.yml @@ -3,5 +3,6 @@ --- ipareplica_packages: [ "freeipa-server", "python3-libselinux" ] ipareplica_packages_dns: [ "freeipa-server-dns" ] +ipareplica_packages_dot: [ "freeipa-server-encrypted-dns" ] ipareplica_packages_adtrust: [ "freeipa-server-trust-ad" ] ipareplica_packages_firewalld: [ "firewalld" ] diff --git a/roles/ipareplica/vars/RedHat-7.yml b/roles/ipareplica/vars/RedHat-7.yml index 34843523..4144b6f2 100644 --- a/roles/ipareplica/vars/RedHat-7.yml +++ b/roles/ipareplica/vars/RedHat-7.yml @@ -3,5 +3,6 @@ --- ipareplica_packages: [ "ipa-server", "libselinux-python" ] ipareplica_packages_dns: [ "ipa-server-dns" ] +ipareplica_packages_dot: [ ] ipareplica_packages_adtrust: [ "ipa-server-trust-ad" ] ipareplica_packages_firewalld: [ "firewalld" ] diff --git a/roles/ipareplica/vars/RedHat-8.yml b/roles/ipareplica/vars/RedHat-8.yml index a1b52994..c97f0659 100644 --- a/roles/ipareplica/vars/RedHat-8.yml +++ b/roles/ipareplica/vars/RedHat-8.yml @@ -3,5 +3,6 @@ --- ipareplica_packages: [ "@idm:DL1/server" ] ipareplica_packages_dns: [ "@idm:DL1/dns" ] +ipareplica_packages_dot: [ ] ipareplica_packages_adtrust: [ "@idm:DL1/adtrust" ] ipareplica_packages_firewalld: [ "firewalld" ] diff --git a/roles/ipareplica/vars/Ubuntu-18.04.yml b/roles/ipareplica/vars/Ubuntu-18.04.yml index b729f362..82667a1e 100644 --- a/roles/ipareplica/vars/Ubuntu-18.04.yml +++ b/roles/ipareplica/vars/Ubuntu-18.04.yml @@ -2,6 +2,7 @@ --- ipareplica_packages: [ "freeipa-server" ] ipareplica_packages_dns: [ "freeipa-server-dns" ] +ipareplica_packages_dot: [ ] ipareplica_packages_adtrust: [ "freeipa-server-trust-ad" ] ipareplica_packages_firewalld: [ "firewalld" ] # Ubuntu Bionic Beaver must use python2 as Python interpreter due diff --git a/roles/ipareplica/vars/Ubuntu.yml b/roles/ipareplica/vars/Ubuntu.yml index 23c2e89d..216f8fad 100644 --- a/roles/ipareplica/vars/Ubuntu.yml +++ b/roles/ipareplica/vars/Ubuntu.yml @@ -3,5 +3,6 @@ --- ipareplica_packages: [ "freeipa-server" ] ipareplica_packages_dns: [ "freeipa-server-dns" ] +ipareplica_packages_dot: [ ] ipareplica_packages_adtrust: [ "freeipa-server-trust-ad" ] ipareplica_packages_firewalld: [ "firewalld" ] diff --git a/roles/ipareplica/vars/default.yml b/roles/ipareplica/vars/default.yml index 0afedeb3..728e8373 100644 --- a/roles/ipareplica/vars/default.yml +++ b/roles/ipareplica/vars/default.yml @@ -1,7 +1,8 @@ # defaults file for ipareplica # vars/default.yml --- -ipareplica_packages: [ "freeipa-server", "python3-libselinux" ] -ipareplica_packages_dns: [ "freeipa-server-dns" ] -ipareplica_packages_adtrust: [ "freeipa-server-trust-ad" ] +ipareplica_packages: [ "ipa-server", "python3-libselinux" ] +ipareplica_packages_dns: [ "ipa-server-dns" ] +ipareplica_packages_dot: [ "ipa-server-encrypted-dns" ] +ipareplica_packages_adtrust: [ "ipa-server-trust-ad" ] ipareplica_packages_firewalld: [ "firewalld" ]