From e22bf295290bff7d6f845bf76d354a9859296234 Mon Sep 17 00:00:00 2001 From: Rafael Guterres Jeffman <rjeffman@redhat.com> Date: Mon, 2 Mar 2020 15:58:40 -0300 Subject: [PATCH] New DNSConfig management module There is a new vaultcontainer management module placed in the plugins folder: plugins/modules/ipadnsconfig.py The dnsconfig module allows to modify global DNS configuration. Here is the documentation for the module: README-dnsconfig.md New example playbooks have been added: playbooks/dnsconfig/set_configuration.yml playbooks/dnsconfig/disable-global-forwarders.yml playbooks/dnsconfig/disallow-reverse-sync.yml New tests for the module: tests/dnsconfig/test_dnsconfig.yml --- README-dnsconfig.md | 140 ++++++++++ README.md | 1 + .../dnsconfig/disable-global-forwarders.yml | 9 + playbooks/dnsconfig/disallow-reverse-sync.yml | 9 + playbooks/dnsconfig/forwarders-absent.yml | 13 + playbooks/dnsconfig/set-configuration.yml | 14 + plugins/modules/ipadnsconfig.py | 257 ++++++++++++++++++ tests/dnsconfig/test_dnsconfig.yml | 141 ++++++++++ 8 files changed, 584 insertions(+) create mode 100644 README-dnsconfig.md create mode 100644 playbooks/dnsconfig/disable-global-forwarders.yml create mode 100644 playbooks/dnsconfig/disallow-reverse-sync.yml create mode 100644 playbooks/dnsconfig/forwarders-absent.yml create mode 100644 playbooks/dnsconfig/set-configuration.yml create mode 100644 plugins/modules/ipadnsconfig.py create mode 100644 tests/dnsconfig/test_dnsconfig.yml diff --git a/README-dnsconfig.md b/README-dnsconfig.md new file mode 100644 index 00000000..029ec515 --- /dev/null +++ b/README-dnsconfig.md @@ -0,0 +1,140 @@ +DNSConfig module +============ + +Description +----------- + +The dnsconfig module allows to modify global DNS configuration. + + +Features +-------- +* Global DNS configuration + + +Supported FreeIPA Versions +-------------------------- + +FreeIPA versions 4.4.0 and up are supported by the ipadnsconfig module. + + +Requirements +------------ + +**Controller** +* Ansible version: 2.8+ + +**Node** +* Supported FreeIPA version (see above) + + +Usage +===== + +Example inventory file + +```ini +[ipaserver] +ipaserver.test.local +``` + +Example playbook to set global DNS configuration: + +```yaml +--- +- name: Playbook to handle global DNS configuration + hosts: ipaserver + become: true + + tasks: + # Set dnsconfig. + - ipadnsconfig: + forwarders: + - ip_address: 8.8.4.4 + - ip_address: 2001:4860:4860::8888 + port: 53 + forward_policy: only + allow_sync_ptr: yes +``` + +Example playbook to ensure a global forwarder, with a custom port, is absent: + +```yaml +--- +- name: Playbook to handle global DNS configuration + hosts: ipaserver + become: true + + tasks: + # Ensure global forwarder with a custom port is absent. + - ipadnsconfig: + forwarders: + - ip_address: 2001:4860:4860::8888 + port: 53 + state: absent +``` + +Example playbook to disable global forwarders: + +```yaml +--- +- name: Playbook to disable global DNS forwarders + hosts: ipaserver + become: true + + tasks: + # Disable global forwarders. + - ipadnsconfig: + forward_policy: none +``` + +Example playbook to change global forward policy: + +```yaml +--- +- name: Playbook to change global forward policy + hosts: ipaserver + become: true + + tasks: + # Disable global forwarders. + - ipadnsconfig: + forward_policy: first +``` + +Example playbook to disallow synchronization of forward (A, AAAA) and reverse (PTR) records: + +```yaml +--- +- name: Playbook to disallow reverse synchronization. + hosts: ipaserver + become: true + + tasks: + # Disable global forwarders. + - ipadnsconfig: + allow_sync_ptr: no +``` + +Variables +========= + +ipadnsconfig +------------ + +Variable | Description | Required +-------- | ----------- | -------- +`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no +`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no +`forwarders` | The list of forwarders dicts. Each `forwarders` dict entry has:| no + | `ip_address` - The IPv4 or IPv6 address of the DNS server. | yes + | `port` - The custom port that should be used on this server. | no +`forward_policy` | The global forwarding policy. It can be one of `only`, `first`, or `none`. | no +`allow_sync_ptr` | Allow synchronization of forward (A, AAAA) and reverse (PTR) records (bool). | yes +`state` | The state to ensure. It can be one of `present` or `absent`, default: `present`. | yes + + +Authors +======= + +Rafael Guterres Jeffman diff --git a/README.md b/README.md index f5d4c257..ebc4e40f 100644 --- a/README.md +++ b/README.md @@ -407,6 +407,7 @@ Roles Modules in plugin/modules ========================= +* [ipadnsconfig](README-dnsconfig.md) * [ipagroup](README-group.md) * [ipahbacrule](README-hbacrule.md) * [ipahbacsvc](README-hbacsvc.md) diff --git a/playbooks/dnsconfig/disable-global-forwarders.yml b/playbooks/dnsconfig/disable-global-forwarders.yml new file mode 100644 index 00000000..3b4f638c --- /dev/null +++ b/playbooks/dnsconfig/disable-global-forwarders.yml @@ -0,0 +1,9 @@ +--- +- name: Playbook to disable global DNS forwarders + hosts: ipaserver + become: true + + tasks: + - name: Disable global forwarders. + ipadnsconfig: + forward_policy: none diff --git a/playbooks/dnsconfig/disallow-reverse-sync.yml b/playbooks/dnsconfig/disallow-reverse-sync.yml new file mode 100644 index 00000000..e99996ef --- /dev/null +++ b/playbooks/dnsconfig/disallow-reverse-sync.yml @@ -0,0 +1,9 @@ +--- +- name: Playbook to disallow reverse record synchronization. + hosts: ipaserver + become: true + + tasks: + - name: Disallow reverse record synchronization. + ipadnsconfig: + allow_sync_ptr: no diff --git a/playbooks/dnsconfig/forwarders-absent.yml b/playbooks/dnsconfig/forwarders-absent.yml new file mode 100644 index 00000000..21a393dd --- /dev/null +++ b/playbooks/dnsconfig/forwarders-absent.yml @@ -0,0 +1,13 @@ +--- +- name: Playbook to handle global DNS configuration + hosts: ipaserver + become: true + + tasks: + - name: Set dnsconfig. + ipadnsconfig: + forwarders: + - ip_address: 8.8.4.4 + - ip_address: 2001:4860:4860::8888 + port: 53 + state: absent diff --git a/playbooks/dnsconfig/set-configuration.yml b/playbooks/dnsconfig/set-configuration.yml new file mode 100644 index 00000000..17880aaf --- /dev/null +++ b/playbooks/dnsconfig/set-configuration.yml @@ -0,0 +1,14 @@ +--- +- name: Playbook to handle global DNS configuration + hosts: ipaserver + become: true + + tasks: + - name: Set dnsconfig. + ipadnsconfig: + forwarders: + - ip_address: 8.8.4.4 + - ip_address: 2001:4860:4860::8888 + port: 53 + forward_policy: only + allow_sync_ptr: yes diff --git a/plugins/modules/ipadnsconfig.py b/plugins/modules/ipadnsconfig.py new file mode 100644 index 00000000..4c9cf2d7 --- /dev/null +++ b/plugins/modules/ipadnsconfig.py @@ -0,0 +1,257 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Authors: +# Rafael Guterres Jeffman <rjeffman@redhat.com> +# +# Copyright (C) 2019 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +ANSIBLE_METADATA = { + "metadata_version": "1.0", + "supported_by": "community", + "status": ["preview"], +} + +DOCUMENTATION = """ +--- +module: ipadnsconfig +short description: Manage FreeIPA dnsconfig +description: Manage FreeIPA dnsconfig +options: + ipaadmin_principal: + description: The admin principal + default: admin + ipaadmin_password: + description: The admin password + required: false + + forwarders: + description: The list of global DNS forwarders. + required: false + options: + ip_address: + description: The forwarder nameserver IP address list (IPv4 and IPv6). + required: true + port: + description: The port to forward requests to. + required: false + forward_policy: + description: + Global forwarding policy. Set to "none" to disable any configured + global forwarders. + required: false + choices: ['only', 'first', 'none'] + allow_sync_ptr: + description: + Allow synchronization of forward (A, AAAA) and reverse (PTR) records. + required: false + type: bool + state: + description: State to ensure + default: present + choices: ["present", "absent"] +""" + +EXAMPLES = """ +# Ensure global DNS forward configuration, allowing PTR record synchronization. +- ipadnsconfig: + forwarders: + - ip_address: 8.8.4.4 + - ip_address: 2001:4860:4860::8888 + port: 53 + forward_policy: only + allow_sync_ptr: yes + +# Ensure forwarder is absent. +- ipadnsconfig: + forwarders: + - ip_address: 2001:4860:4860::8888 + port: 53 + state: absent + +# Disable PTR record synchronization. +- ipadnsconfig: + allow_sync_ptr: no + +# Disable global forwarders. +- ipadnsconfig: + forward_policy: none +""" + +RETURN = """ +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_text +from ansible.module_utils.ansible_freeipa_module import temp_kinit, \ + temp_kdestroy, valid_creds, api_connect, api_command, \ + api_command_no_name, compare_args_ipa, module_params_get, \ + gen_add_del_lists, is_ipv4_addr, is_ipv6_addr, ipalib_errors + + +def find_dnsconfig(module): + _args = { + "all": True, + } + + _result = api_command_no_name(module, "dnsconfig_show", _args) + + if "result" in _result: + if _result["result"].get('idnsforwarders', None) is None: + _result["result"]['idnsforwarders'] = [''] + return _result["result"] + else: + module.fail("Could not retrieve current DNS configuration.") + return None + + +def gen_args(module, state, dnsconfig, forwarders, forward_policy, + allow_sync_ptr): + _args = {} + + if forwarders: + _forwarders = [] + for forwarder in forwarders: + ip_address = forwarder.get('ip_address') + port = forwarder.get('port') + if not (is_ipv4_addr(ip_address) or is_ipv6_addr(ip_address)): + module.fail( + msg="Invalid IP for DNS forwarder: %s" % ip_address) + if port is None: + _forwarders.append(ip_address) + else: + _forwarders.append('%s port %d' % (ip_address, port)) + + global_forwarders = dnsconfig.get('idnsforwarders', []) + if state == 'absent': + _args['idnsforwarders'] = [ + fwd for fwd in global_forwarders if fwd not in _forwarders] + # When all forwarders should be excluded, use an empty string (''). + if not _args['idnsforwarders']: + _args['idnsforwarders'] = [''] + + elif state == 'present': + _args['idnsforwarders'] = [ + fwd for fwd in _forwarders if fwd not in global_forwarders] + # If no forwarders should be added, remove argument. + if not _args['idnsforwarders']: + del _args['idnsforwarders'] + + else: + # shouldn't happen, but let's be paranoid. + module.fail(msg="Invalid state: %s" % state) + + if forward_policy is not None: + _args['idnsforwardpolicy'] = forward_policy + + if allow_sync_ptr is not None: + _args['idnsallowsyncptr'] = 'TRUE' if allow_sync_ptr else 'FALSE' + + return _args + + +def main(): + forwarder_spec = dict( + ip_address=dict(type=str, required=True), + port=dict(type=int, required=False, default=None) + ) + + ansible_module = AnsibleModule( + argument_spec=dict( + # general + ipaadmin_principal=dict(type='str', default='admin'), + ipaadmin_password=dict(type='str', no_log=True), + + # dnsconfig + forwarders=dict(type='list', default=None, required=False, + options=dict(**forwarder_spec)), + forward_policy=dict(type='str', required=False, default=None, + choices=['only', 'first', 'none']), + allow_sync_ptr=dict(type='bool', required=False, default=None), + + # general + state=dict(type="str", default="present", + choices=["present", "absent"]), + + ) + ) + + ansible_module._ansible_debug = True + + # general + ipaadmin_principal = module_params_get(ansible_module, + "ipaadmin_principal") + ipaadmin_password = module_params_get(ansible_module, + "ipaadmin_password") + + forwarders = module_params_get(ansible_module, 'forwarders') or [] + forward_policy = module_params_get(ansible_module, 'forward_policy') + allow_sync_ptr = module_params_get(ansible_module, 'allow_sync_ptr') + + state = module_params_get(ansible_module, 'state') + + # Check parameters. + invalid = [] + if state == 'absent': + invalid = ['forward_policy', 'allow_sync_ptr'] + + for x in invalid: + if vars()[x] is not None: + ansible_module.fail_json( + msg="Argument '%s' can not be used with state '%s'" % + (x, state)) + + # Init + + changed = False + ccache_dir = None + ccache_name = None + try: + if not valid_creds(ansible_module, ipaadmin_principal): + ccache_dir, ccache_name = temp_kinit(ipaadmin_principal, + ipaadmin_password) + api_connect() + + res_find = find_dnsconfig(ansible_module) + args = gen_args(ansible_module, state, res_find, forwarders, + forward_policy, allow_sync_ptr) + + # Execute command only if configuration changes. + if not compare_args_ipa(ansible_module, args, res_find): + try: + api_command_no_name(ansible_module, 'dnsconfig_mod', args) + # If command did not fail, something changed. + changed = True + + except Exception as e: + msg = str(e) + ansible_module.fail_json(msg="dnsconfig_mod: %s" % msg) + + except Exception as e: + ansible_module.fail_json(msg=str(e)) + + finally: + temp_kdestroy(ccache_dir, ccache_name) + + # Done + + ansible_module.exit_json(changed=changed) + + +if __name__ == "__main__": + main() diff --git a/tests/dnsconfig/test_dnsconfig.yml b/tests/dnsconfig/test_dnsconfig.yml new file mode 100644 index 00000000..1e1b1094 --- /dev/null +++ b/tests/dnsconfig/test_dnsconfig.yml @@ -0,0 +1,141 @@ +--- +- name: Test dnsconfig + hosts: ipaserver + become: true + gather_facts: true + + tasks: + # Setup. + - name: Ensure forwarders are absent. + ipadnsconfig: + forwarders: + - ip_address: 8.8.8.8 + - ip_address: 8.8.4.4 + - ip_address: 2001:4860:4860::8888 + - ip_address: 2001:4860:4860::8888 + port: 53 + state: absent + + # Tests. + + - name: Set dnsconfig. + ipadnsconfig: + forwarders: + - ip_address: 8.8.8.8 + - ip_address: 8.8.4.4 + - ip_address: 2001:4860:4860::8888 + port: 53 + forward_policy: only + allow_sync_ptr: yes + register: result + failed_when: not result.changed + + - name: Set dnsconfig, with the same values. + ipadnsconfig: + forwarders: + - ip_address: 8.8.8.8 + - ip_address: 8.8.4.4 + - ip_address: 2001:4860:4860::8888 + port: 53 + forward_policy: only + allow_sync_ptr: yes + register: result + failed_when: result.changed + + - name: Ensure forwarder is absent. + ipadnsconfig: + forwarders: + - ip_address: 8.8.8.8 + state: absent + register: result + failed_when: not result.changed + + - name: Ensure forwarder is absent, again. + ipadnsconfig: + forwarders: + - ip_address: 8.8.8.8 + state: absent + register: result + failed_when: result.changed + + - name: Disable global forwarders. + ipadnsconfig: + forward_policy: none + register: result + failed_when: not result.changed + + - name: Disable global forwarders, again. + ipadnsconfig: + forward_policy: none + register: result + failed_when: result.changed + + - name: Re-enable global forwarders. + ipadnsconfig: + forward_policy: first + register: result + failed_when: not result.changed + + - name: Re-enable global forwarders, again. + ipadnsconfig: + forward_policy: first + register: result + failed_when: result.changed + + - name: Disable PTR record synchronization. + ipadnsconfig: + allow_sync_ptr: no + register: result + failed_when: not result.changed + + - name: Disable PTR record synchronization, again. + ipadnsconfig: + allow_sync_ptr: no + register: result + failed_when: result.changed + + - name: Re-enable PTR record synchronization. + ipadnsconfig: + allow_sync_ptr: yes + register: result + failed_when: not result.changed + + - name: Re-enable PTR record synchronization, again. + ipadnsconfig: + allow_sync_ptr: yes + register: result + failed_when: result.changed + + - name: Ensure all forwarders are absent. + ipadnsconfig: + forwarders: + - ip_address: 8.8.8.8 + - ip_address: 8.8.4.4 + - ip_address: 2001:4860:4860::8888 + port: 53 + state: absent + register: result + failed_when: not result.changed + + + - name: Ensure all forwarders are absent, again. + ipadnsconfig: + forwarders: + - ip_address: 8.8.8.8 + - ip_address: 8.8.4.4 + - ip_address: 2001:4860:4860::8888 + port: 53 + state: absent + register: result + failed_when: result.changed + + # Cleanup. + - name: Ensure forwarders are absent. + ipadnsconfig: + forwarders: + - ip_address: 8.8.8.8 + - ip_address: 8.8.4.4 + - ip_address: 2001:4860:4860::8888 + - ip_address: 2001:4860:4860::8888 + port: 53 + state: absent -- GitLab