From 7a23c668fc9cf08a29e27246e2ab4957b530ed53 Mon Sep 17 00:00:00 2001 From: Thomas Woerner Date: Tue, 1 Jul 2025 14:19:13 +0200 Subject: [PATCH] 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" ]