Skip to content
Snippets Groups Projects
Commit 708675d9 authored by chrisp's avatar chrisp
Browse files

add a module to manage dns forwarder zones in ipa

parent bf1e53cb
No related branches found
No related tags found
No related merge requests found
Dnsforwardzone module
=====================
Description
-----------
The dnsforwardzone module allows the addition and removal of dns forwarders from the IPA DNS config.
It is desgined to follow the IPA api as closely as possible while ensuring ease of use.
Features
--------
* DNS zone management
Supported FreeIPA Versions
--------------------------
FreeIPA versions 4.4.0 and up are supported by the ipadnsforwardzone 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 ensure presence of a forwardzone to ipa DNS:
```yaml
---
- name: Playbook to handle add a forwarder
hosts: ipaserver
become: true
tasks:
- name: ensure presence of forwardzone for DNS requests for example.com to 8.8.8.8
ipadnsforwardzone:
ipaadmin_password: password01
state: present
name: example.com
forwarders:
- 8.8.8.8
forwardpolicy: first
skip_overlap_check: true
- name: ensure the forward zone is disabled
ipadnsforwardzone:
ipaadmin_password: password01
name: example.com
state: disabled
- name: ensure presence of multiple upstream DNS servers for example.com
ipadnsforwardzone:
ipaadmin_password: password01
state: present
name: example.com
forwarders:
- 8.8.8.8
- 4.4.4.4
- name: ensure presence of another forwarder to any existing ones for example.com
ipadnsforwardzone:
ipaadmin_password: password01
state: present
name: example.com
forwarders:
- 1.1.1.1
action: member
- name: ensure the forwarder for example.com does not exists (delete it if needed)
ipadnsforwardzone:
ipaadmin_password: password01
name: example.com
state: absent
```
Variables
=========
ipagroup
-------
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
`name` \| `cn` | Zone name (FQDN). | yes if `state` == `present`
`forwarders` \| `idnsforwarders` | Per-zone conditional forwarding policy. Possible values are `only`, `first`, `none`) | no
`forwardpolicy` \| `idnsforwardpolicy` | Per-zone conditional forwarding policy. Set to "none" to disable forwarding to global forwarder for this zone. In that case, conditional zone forwarders are disregarded. | no
`skip_overlap_check` | Force DNS zone creation even if it will overlap with an existing zone. Defaults to False. | no
`action` | Work on group or member level. It can be on of `member` or `dnsforwardzone` and defaults to `dnsforwardzone`. | no
`state` | The state to ensure. It can be one of `present`, `absent`, `enabled` or `disabled`, default: `present`. | yes
Authors
=======
Chris Procter
...@@ -11,6 +11,7 @@ Features ...@@ -11,6 +11,7 @@ Features
* Cluster deployments: Server, replicas and clients in one playbook * Cluster deployments: Server, replicas and clients in one playbook
* One-time-password (OTP) support for client installation * One-time-password (OTP) support for client installation
* Repair mode for clients * Repair mode for clients
* Modules for dns forwarder management
* Modules for group management * Modules for group management
* Modules for hbacrule management * Modules for hbacrule management
* Modules for hbacsvc management * Modules for hbacsvc management
...@@ -403,6 +404,7 @@ Roles ...@@ -403,6 +404,7 @@ Roles
Modules in plugin/modules Modules in plugin/modules
========================= =========================
* [ipadnsforwardzone](README-dnsforwardzone.md)
* [ipagroup](README-group.md) * [ipagroup](README-group.md)
* [ipahbacrule](README-hbacrule.md) * [ipahbacrule](README-hbacrule.md)
* [ipahbacsvc](README-hbacsvc.md) * [ipahbacsvc](README-hbacsvc.md)
......
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Authors:
# Chris Procter <cprocter@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: ipa_dnsforwardzone
author: chris procter
short_description: Manage FreeIPA DNS Forwarder Zones
description:
- Add and delete an IPA DNS Forwarder Zones using IPA API
options:
ipaadmin_principal:
description: The admin principal
default: admin
ipaadmin_password:
description: The admin password
required: false
name:
description:
- The DNS zone name which needs to be managed.
required: true
aliases: ["cn"]
state:
description: State to ensure
required: false
default: present
choices: ["present", "absent", "enabled", "disabled"]
forwarders:
description:
- List of the DNS servers to forward to
required: true
type: list
aliases: ["idnsforwarders"]
forwardpolicy:
description: Per-zone conditional forwarding policy
required: false
default: only
choices: ["only", "first", "none"]
aliases: ["idnsforwarders"]
skip_overlap_check:
description:
- Force DNS zone creation even if it will overlap with an existing zone.
required: false
default: false
'''
EXAMPLES = '''
# Ensure dns zone is present
- ipadnsforwardzone:
ipaadmin_password: MyPassword123
state: present
name: example.com
forwarders:
- 8.8.8.8
- 4.4.4.4
forwardpolicy: first
skip_overlap_check: true
# Ensure that dns zone is removed
- ipadnsforwardzone:
ipaadmin_password: MyPassword123
name: example.com
state: absent
'''
RETURN = '''
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa, \
module_params_get
def find_dnsforwardzone(module, name):
_args = {
"all": True,
"idnsname": name
}
_result = api_command(module, "dnsforwardzone_find", name, _args)
if len(_result["result"]) > 1:
module.fail_json(
msg="There is more than one dnsforwardzone '%s'" % (name))
elif len(_result["result"]) == 1:
return _result["result"][0]
else:
return None
def gen_args(forwarders, forwardpolicy, skip_overlap_check):
_args = {}
if forwarders is not None:
_args["idnsforwarders"] = forwarders
if forwardpolicy is not None:
_args["idnsforwardpolicy"] = forwardpolicy
if skip_overlap_check is not None:
_args["skip_overlap_check"] = skip_overlap_check
return _args
def main():
ansible_module = AnsibleModule(
argument_spec=dict(
# general
ipaadmin_principal=dict(type="str", default="admin"),
ipaadmin_password=dict(type="str", required=False, no_log=True),
name=dict(type="str", aliases=["cn"], default=None,
required=True),
forwarders=dict(type='list', aliases=["idnsforwarders"],
required=False),
forwardpolicy=dict(type='str', aliases=["idnsforwardpolicy"],
required=False,
choices=['only', 'first', 'none']),
skip_overlap_check=dict(type='bool', required=False),
action=dict(type="str", default="dnsforwardzone",
choices=["member", "dnsforwardzone"]),
# state
state=dict(type='str', default='present',
choices=['present', 'absent', 'enabled', 'disabled']),
),
supports_check_mode=True,
)
ansible_module._ansible_debug = True
# Get parameters
ipaadmin_principal = module_params_get(ansible_module,
"ipaadmin_principal")
ipaadmin_password = module_params_get(ansible_module,
"ipaadmin_password")
name = module_params_get(ansible_module, "name")
action = module_params_get(ansible_module, "action")
forwarders = module_params_get(ansible_module, "forwarders")
forwardpolicy = module_params_get(ansible_module, "forwardpolicy")
skip_overlap_check = module_params_get(ansible_module,
"skip_overlap_check")
state = module_params_get(ansible_module, "state")
# absent stae means delete if the action is NOT member but update if it is
# if action is member then update an exisiting resource
# and if action is not member then create a resource
if state == "absent" and action == "dnsforwardzone":
operation = "del"
elif action == "member":
operation = "update"
else:
operation = "add"
if state == "disabled":
wants_enable = False
else:
wants_enable = True
if operation == "del":
invalid = ["forwarders", "forwardpolicy", "skip_overlap_check"]
for x in invalid:
if vars()[x] is not None:
ansible_module.fail_json(
msg="Argument '%s' can not be used with action "
"'%s'" % (x, action))
changed = False
exit_args = {}
args = {}
ccache_dir = None
ccache_name = None
is_enabled = "IGNORE"
try:
# we need to determine 3 variables
# args = the values we want to change/set
# command = the ipa api command to call del, add, or mod
# is_enabled = is the current resource enabled (True)
# disabled (False) and do we care (IGNORE)
if not valid_creds(ansible_module, ipaadmin_principal):
ccache_dir, ccache_name = temp_kinit(ipaadmin_principal,
ipaadmin_password)
api_connect()
# Make sure forwardzone exists
existing_resource = find_dnsforwardzone(ansible_module, name)
if existing_resource is None and operation == "update":
# does not exist and is updating
# trying to update something that doesn't exist, so error
ansible_module.fail_json(msg="""dnsforwardzone '%s' is not
valid""" % (name))
elif existing_resource is None and operation == "del":
# does not exists and should be absent
# set command
command = None
# enabled or disabled?
is_enabled = "IGNORE"
elif existing_resource is not None and operation == "del":
# exists but should be absent
# set command
command = "dnsforwardzone_del"
# enabled or disabled?
is_enabled = "IGNORE"
elif forwarders is None:
# forwarders are not defined its not a delete, update state?
# set command
command = None
# enabled or disabled?
if existing_resource is not None:
is_enabled = existing_resource["idnszoneactive"][0]
else:
is_enabled = "IGNORE"
elif existing_resource is not None and operation == "update":
# exists and is updating
# calculate the new forwarders and mod
# determine args
if state != "absent":
forwarders = list(set(existing_resource["idnsforwarders"]
+ forwarders))
else:
forwarders = list(set(existing_resource["idnsforwarders"])
- set(forwarders))
args = gen_args(forwarders, forwardpolicy,
skip_overlap_check)
if skip_overlap_check is not None:
del args['skip_overlap_check']
# command
if not compare_args_ipa(ansible_module, args, existing_resource):
command = "dnsforwardzone_mod"
else:
command = None
# enabled or disabled?
is_enabled = existing_resource["idnszoneactive"][0]
elif existing_resource is None and operation == "add":
# does not exist but should be present
# determine args
args = gen_args(forwarders, forwardpolicy,
skip_overlap_check)
# set command
command = "dnsforwardzone_add"
# enabled or disabled?
is_enabled = "TRUE"
elif existing_resource is not None and operation == "add":
# exists and should be present, has it changed?
# determine args
args = gen_args(forwarders, forwardpolicy, skip_overlap_check)
if skip_overlap_check is not None:
del args['skip_overlap_check']
# set command
if not compare_args_ipa(ansible_module, args, existing_resource):
command = "dnsforwardzone_mod"
else:
command = None
# enabled or disabled?
is_enabled = existing_resource["idnszoneactive"][0]
# if command is set then run it with the args
if command is not None:
api_command(ansible_module, command, name, args)
changed = True
# does the enabled state match what we want (if we care)
if is_enabled != "IGNORE":
if wants_enable and is_enabled != "TRUE":
api_command(ansible_module, "dnsforwardzone_enable",
name, {})
changed = True
elif not wants_enable and is_enabled != "FALSE":
api_command(ansible_module, "dnsforwardzone_disable",
name, {})
changed = True
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, dnsforwardzone=exit_args)
if __name__ == "__main__":
main()
---
- name: Test dnsforwardzone
hosts: ipaserver
become: true
gather_facts: false
tasks:
- name: ensure forwardzone example.com is absent - prep
ipadnsforwardzone:
ipaadmin_password: password01
name: example.com
state: absent
- name: ensure forwardzone example.com is created
ipadnsforwardzone:
ipaadmin_password: password01
state: present
name: example.com
forwarders:
- 8.8.8.8
forwardpolicy: first
skip_overlap_check: true
register: result
failed_when: not result.changed
- name: ensure forwardzone example.com is present again
ipadnsforwardzone:
ipaadmin_password: password01
state: present
name: example.com
forwarders:
- 8.8.8.8
forwardpolicy: first
skip_overlap_check: true
register: result
failed_when: result.changed
- name: ensure forwardzone example.com has two forwarders
ipadnsforwardzone:
ipaadmin_password: password01
state: present
name: example.com
forwarders:
- 8.8.8.8
- 4.4.4.4
forwardpolicy: first
skip_overlap_check: true
register: result
failed_when: not result.changed
- name: ensure forwardzone example.com has one forwarder again
ipadnsforwardzone:
ipaadmin_password: password01
name: example.com
forwarders:
- 8.8.8.8
forwardpolicy: first
skip_overlap_check: true
state: present
register: result
failed_when: not result.changed
- name: skip_overlap_check can only be set on creation so change nothing
ipadnsforwardzone:
ipaadmin_password: password01
name: example.com
forwarders:
- 8.8.8.8
forwardpolicy: first
skip_overlap_check: false
state: present
register: result
failed_when: result.changed
- name: change all the things at once
ipadnsforwardzone:
ipaadmin_password: password01
state: present
name: example.com
forwarders:
- 8.8.8.8
- 4.4.4.4
forwardpolicy: only
skip_overlap_check: false
register: result
failed_when: not result.changed
- name: ensure forwardzone example.com is absent for next testset
ipadnsforwardzone:
ipaadmin_password: password01
name: example.com
state: absent
- name: ensure forwardzone example.com is created with minimal args
ipadnsforwardzone:
ipaadmin_password: password01
state: present
name: example.com
skip_overlap_check: true
forwarders:
- 8.8.8.8
register: result
failed_when: not result.changed
- name: add a forwarder to any existing ones
ipadnsforwardzone:
ipaadmin_password: password01
state: present
name: example.com
forwarders:
- 4.4.4.4
action: member
register: result
failed_when: not result.changed
- name: check the list of forwarders is what we expect
ipadnsforwardzone:
ipaadmin_password: password01
state: present
name: example.com
forwarders:
- 4.4.4.4
- 8.8.8.8
action: member
register: result
failed_when: result.changed
- name: remove a single forwarder
ipadnsforwardzone:
ipaadmin_password: password01
state: absent
name: example.com
forwarders:
- 8.8.8.8
action: member
register: result
failed_when: not result.changed
- name: check the list of forwarders is what we expect now
ipadnsforwardzone:
ipaadmin_password: password01
state: present
name: example.com
forwarders:
- 4.4.4.4
action: member
register: result
failed_when: result.changed
- name: ensure forwardzone example.com is absent again
ipadnsforwardzone:
ipaadmin_password: password01
name: example.com
state: absent
- name: try to create a new forwarder with action=member
ipadnsforwardzone:
ipaadmin_password: password01
state: present
name: example.com
forwarders:
- 4.4.4.4
action: member
skip_overlap_check: true
register: result
failed_when: result.changed
- name: ensure forwardzone example.com is absent - tidy up
ipadnsforwardzone:
ipaadmin_password: password01
name: example.com
state: absent
- name: try to create a new forwarder is disabled state
ipadnsforwardzone:
ipaadmin_password: password01
state: disabled
name: example.com
forwarders:
- 4.4.4.4
skip_overlap_check: true
register: result
failed_when: not result.changed
- name: enable the forwarder
ipadnsforwardzone:
ipaadmin_password: password01
name: example.com
state: enabled
register: result
failed_when: not result.changed
- name: disable the forwarder again
ipadnsforwardzone:
ipaadmin_password: password01
name: example.com
state: disabled
action: member
register: result
failed_when: not result.changed
- name: ensure it stays disabled
ipadnsforwardzone:
ipaadmin_password: password01
name: example.com
state: disabled
register: result
failed_when: result.changed
- name: ensure forwardzone example.com is absent - tidy up
ipadnsforwardzone:
ipaadmin_password: password01
name: example.com
state: absent
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment