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" ]