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" ] 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 da7197b7..d87e51a2 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) @@ -339,12 +347,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" ] 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 1903bc37..7842271f 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) @@ -356,13 +364,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" ]