Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Add unbound DNSBL support #109

Merged
merged 3 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/source/modules/2_list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ In most cases the returned type of this module ist a list of dictionaries.
:header: "Parameter", "Type", "Required", "Default", "Aliases", "Comment"
:widths: 15 10 10 10 10 45

"target","string","true","\-","tgt, t","What part of the running config should be queried/listed. One of: 'alias', 'rule', 'route', 'cron', 'syslog', 'package', 'unbound_general', 'unbound_acl', 'unbound_host', 'unbound_domain', 'unbound_dot', 'unbound_forward', 'unbound_host_alias', 'ipsec_cert', 'shaper_pipe', 'shaper_queue', 'shaper_rule', 'monit_service', 'monit_test', 'monit_alert', 'wireguard_server', 'wireguard_peer', 'interface_lagg', 'interface_vlan', 'interface_vxlan', 'source_nat', 'frr_bfd', 'frr_bgp_general', 'frr_bgp_neighbor', 'frr_bgp_prefix_list', 'frr_bgp_community_list', 'frr_bgp_as_path', 'frr_bgp_route_map', 'frr_ospf_general', 'frr_ospf_prefix_list', 'frr_ospf_interface', 'frr_ospf_route_map', 'frr_ospf_network', 'frr_ospf3_general', 'frr_ospf3_interface', 'frr_rip', 'bind_general', 'bind_blocklist', 'bind_acl', 'bind_domain', 'bind_record', 'interface_vip', 'webproxy_general', 'webproxy_cache', 'webproxy_parent', 'webproxy_traffic', 'webproxy_forward', 'webproxy_acl', 'webproxy_icap', 'webproxy_auth', 'webproxy_remote_acl', 'webproxy_pac_proxy', 'webproxy_pac_match', 'webproxy_pac_rule'"
"target","string","true","\-","tgt, t","What part of the running config should be queried/listed. One of: 'alias', 'rule', 'route', 'cron', 'syslog', 'package', 'unbound_general', 'unbound_acl', 'unbound_host', 'unbound_domain', 'unbound_dot', 'unbound_forward', 'unbound_host_alias', 'ipsec_cert', 'shaper_pipe', 'shaper_queue', 'shaper_rule', 'monit_service', 'monit_test', 'monit_alert', 'wireguard_server', 'wireguard_peer', 'interface_lagg', 'interface_vlan', 'interface_vxlan', 'source_nat', 'frr_bfd', 'frr_bgp_general', 'frr_bgp_neighbor', 'frr_bgp_prefix_list', 'frr_bgp_community_list', 'frr_bgp_as_path', 'frr_bgp_route_map', 'frr_ospf_general', 'frr_ospf_prefix_list', 'frr_ospf_interface', 'frr_ospf_route_map', 'frr_ospf_network', 'frr_ospf3_general', 'frr_ospf3_interface', 'frr_rip', 'bind_general', 'bind_blocklist', 'bind_acl', 'bind_domain', 'bind_record', 'interface_vip', 'webproxy_general', 'webproxy_cache', 'webproxy_parent', 'webproxy_traffic', 'webproxy_forward', 'webproxy_acl', 'webproxy_icap', 'webproxy_auth', 'webproxy_remote_acl', 'webproxy_pac_proxy', 'webproxy_pac_match', 'webproxy_pac_rule', 'unbound_dnsbl'"

.. include:: ../_include/param_basic.rst

Expand Down
88 changes: 88 additions & 0 deletions docs/source/modules/unbound_dnsbl.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
.. _modules_unbound_dnsbl:

.. include:: ../_include/head.rst

============
MODULE TITLE
============

**STATE**: unstable

**TESTS**: `Playbook <https://github.com/ansibleguy/collection_opnsense/blob/latest/tests/unbound_dnsbl.yml>`_

**API Docs**: `unbound_dnsbl <https://docs.opnsense.org/development/api/core/unbound.html>`_

**Service Docs**: `Unbound DNS - Blocklists <https://docs.opnsense.org/manual/unbound.html#blocklists>`_


Definition
**********

.. csv-table:: Definition
:header: "Parameter", "Type", "Required", "Default", "Aliases", "Comment"
:widths: 15 10 10 10 10 45

"parameter name","parameter type","if is required","default value","aliases","description"
"safesearch","boolean","false","false","\-","Force the usage of SafeSearch on Google, DuckDuckGo, Bing, Qwant, PixaBay and YouTube"
"type","list of strings","false","[]","\-","Select which kind of DNSBL you want to use"
"whitelists","list of strings","false","[]","whitelist, allowlist, allowlists","List of domains to whitelist. You can use regular expressions"
"blocklists","list of strings","false","[]","blocklist","List of domains to blocklist. Only exact matches are supported"
"wildcards","list of strings","false","[]","wildcard","List of wildcard domains to blocklist. All subdomains of the given domain will be blocked. Blocking first-level domains is not supported"
"address","strings","false","","\-","Destination ip address for entries in the blocklist (leave empty to use default: 0.0.0.0). Not used when "Return NXDOMAIN" is checked"
"nxdomain","bool","false","false","\-","Use the DNS response code NXDOMAIN instead of a destination address"
"enabled","boolean","false","true","\-","Enable the usage of DNS blocklists"
"reload","boolean","false","true","\-", .. include:: ../_include/param_reload.rst

.. include:: ../_include/param_basic.rst

Usage
*****

Manage Unbound DNS blocklists and exceptions.

Examples
********

.. code-block:: yaml

- hosts: localhost
gather_facts: false
module_defaults:
group/ansibleguy.opnsense.all:
firewall: 'opnsense.template.ansibleguy.net'
api_credential_file: '/home/guy/.secret/opn.key'

ansibleguy.opnsense.list:
target: 'unbound_dnsbl'

tasks:
# add optional parameters commented-out
# required ones normally
# add their default values to get a brief overview of how the module works
- name: Example
ansibleguy.opnsense.unbound_dnsbl:
type: atl
# safesearch: false
# lists: ['https://example.com/dns.blocklist']
# whitelists: ['ansibleguy.net']
# blocklists: ['example.net']
# wildcards: ['example.net']
# address: 192.168.254.254
# nxdomain: false
# enable: false
# state: 'absent'
# debug: false

- name: Configuring DNS Blocklists
ansibleguy.opnsense.unbound_dnsbl:
type: atl
enable: true

- name: Listing current config
ansibleguy.opnsense.list:
# target: 'unbound_dnsbl'
register: dnsbl_config

- name: Printing
ansible.builtin.debug:
var: dnsbl_config.data
1 change: 1 addition & 0 deletions meta/runtime.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ action_groups:
- ansibleguy.opnsense.unbound_host
- ansibleguy.opnsense.unbound_domain
- ansibleguy.opnsense.unbound_host_alias
- ansibleguy.opnsense.unbound_dnsbl
ipsec:
- ansibleguy.opnsense.ipsec_auth_local
- ansibleguy.opnsense.ipsec_auth_remote
Expand Down
36 changes: 36 additions & 0 deletions plugins/module_utils/main/unbound_dnsbl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from ansible.module_utils.basic import AnsibleModule

from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.api import \
Session
from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.cls import GeneralModule


# Supported as of OPNsense 23.7
class DnsBL(GeneralModule):
CMDS = {
'set': 'set',
'search': 'get',
}
API_KEY_PATH = 'unbound.dnsbl'
API_KEY_PATH_REQ = API_KEY_PATH
API_MOD = 'unbound'
API_CONT = 'settings'
API_CONT_REL = 'service'
API_CMD_REL = 'reconfigureGeneral'
FIELDS_CHANGE = [
'enabled', 'safesearch', 'type', 'lists', 'whitelists', 'blocklists', 'wildcards', 'address', 'nxdomain'
]
FIELDS_ALL = FIELDS_CHANGE
FIELDS_TYPING = {
'bool': ['enabled', 'safesearch', 'nxdomain'],
'list': ['type', 'lists', 'whitelists', 'blocklists', 'wildcards'],
}

def __init__(self, module: AnsibleModule, result: dict, session: Session = None):
GeneralModule.__init__(self=self, m=module, r=result, s=session)

def check(self) -> None:
# pylint: disable=W0201
self.settings = self._search_call()

self._build_diff()
6 changes: 5 additions & 1 deletion plugins/modules/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
'ipsec_child', 'ipsec_vti', 'ipsec_auth_local', 'ipsec_auth_remote', 'frr_general', 'unbound_general',
'unbound_acl', 'ids_general', 'ids_policy', 'ids_rule', 'ids_ruleset', 'ids_user_rule', 'ids_policy_rule',
'openvpn_instance', 'openvpn_static_key', 'openvpn_client_override', 'dhcrelay_destination', 'dhcrelay_relay',
'interface_lagg', 'interface_loopback',
'interface_lagg', 'interface_loopback', 'unbound_dnsbl',
]


Expand Down Expand Up @@ -119,6 +119,10 @@ def run_module():
from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.unbound_forward \
import Forward as Target_Obj

elif target == 'unbound_dnsbl':
from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.unbound_dnsbl import \
DnsBL as Target_Obj

elif target == 'syslog':
from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.syslog import \
Syslog as Target_Obj
Expand Down
91 changes: 91 additions & 0 deletions plugins/modules/unbound_dnsbl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright: (C) 2024, AnsibleGuy <guy@ansibleguy.net>
# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt)

# see: https://docs.opnsense.org/development/api/plugins/unbound.html

from ansible.module_utils.basic import AnsibleModule

from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.handler import \
module_dependency_error, MODULE_EXCEPTIONS

try:
from ansible_collections.ansibleguy.opnsense.plugins.module_utils.helper.wrapper import module_wrapper
from ansible_collections.ansibleguy.opnsense.plugins.module_utils.defaults.main import \
OPN_MOD_ARGS, EN_ONLY_MOD_ARG, RELOAD_MOD_ARG
from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.unbound_dnsbl import DnsBL

except MODULE_EXCEPTIONS:
module_dependency_error()


# DOCUMENTATION = 'https://opnsense.ansibleguy.net/en/latest/modules/unbound_general.html'
# EXAMPLES = 'https://opnsense.ansibleguy.net/en/latest/modules/unbound_general.html'


def run_module():
module_args = dict(
safesearch=dict(
type='bool', required=False, default=False,
description='Force the usage of SafeSearch on Google, DuckDuckGo, Bing, Qwant, PixaBay and YouTube'
),
type=dict(
type='list', elements='str', required=False, default=[], aliases=['bl'],
description='Select which kind of DNSBL you want to use'
),
lists=dict(
type='list', elements='str', required=False, default=[], aliases=['list'],
description='List of urls from where blocklist will be downloaded'
),
whitelists=dict(
type='list', elements='str', required=False, default=[], aliases=['whitelist', 'allowlist', 'allowlists'],
description='List of domains to whitelist. You can use regular expressions'
),
blocklists=dict(
type='list', elements='str', required=False, default=[], aliases=['blocklist'],
description='List of domains to blocklist. Only exact matches are supported'
),
wildcards=dict(
type='list', elements='str', required=False, default=[], aliases=['wildcard'],
description='List of wildcard domains to blocklist. All subdomains of the given domain will be blocked. '
'Blocking first-level domains is not supported'
),
address=dict(
type='str', required=False,
description='Destination ip address for entries in the blocklist (leave empty to use default: 0.0.0.0). '
'Not used when "Return NXDOMAIN" is checked'
),
nxdomain=dict(
type='bool', required=False, default=False,
description='Use the DNS response code NXDOMAIN instead of a destination address'
),
**EN_ONLY_MOD_ARG,
**OPN_MOD_ARGS,
**RELOAD_MOD_ARG,
)

result = dict(
changed=False,
diff={
'before': {},
'after': {},
}
)

module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True,
)

module_wrapper(DnsBL(module=module, result=result))
module.exit_json(**result)


def main():
run_module()


if __name__ == '__main__':
main()
123 changes: 123 additions & 0 deletions tests/unbound_dnsbl.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
---

Check failure on line 1 in tests/unbound_dnsbl.yml

View workflow job for this annotation

GitHub Actions / lint

1:4 [new-lines] wrong new line character: expected \n

- name: Testing Unbound DNS general settings
hosts: localhost
gather_facts: no
module_defaults:
group/ansibleguy.opnsense.all:
firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}"
api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}"
ssl_verify: false

ansibleguy.opnsense.list:
target: 'unbound_dnsbl'

tasks:
- name: Listing
ansibleguy.opnsense.list:
register: opn_pre1
failed_when: >
opn_pre1.failed or
'data' not in opn_pre1

- name: Configuring - failing because of invalid type
ansibleguy.opnsense.unbound_dnsbl:
type:
- ANSIBLE_TEST_1
register: opn_fail1
failed_when: not opn_fail1.failed
when: not ansible_check_mode

- name: Configuring - failing because of invalid address
ansibleguy.opnsense.unbound_dnsbl:
address: INVALID
register: opn_fail2
failed_when: not opn_fail2.failed
when: not ansible_check_mode

- name: Reset Configuring
ansibleguy.opnsense.unbound_dnsbl:
when: not ansible_check_mode

- name: Configuring
ansibleguy.opnsense.unbound_dnsbl:
type:
- atf
lists: ['https://example.com/dns.blocklist']
whitelists:
- opnsense.org
- ansibleguy.net
blocklists:
- example.tor
wildcards:
- example.tor
address: 192.168.255.255
nxdomain: false
enabled: true
register: opn1
failed_when: >
opn1.failed or
not opn1.changed

- name: Changing
ansibleguy.opnsense.unbound_dnsbl:
type:
- atf
whitelists:
- opnsense.org
- ansibleguy.net
blocklists:
- example.tor
wildcards:
- example.tor
nxdomain: true
enabled: true
register: opn2
failed_when: >
opn2.failed or
not opn2.changed

- name: Disabling 1
ansibleguy.opnsense.unbound_dnsbl:
type:
- atf
whitelists:
- opnsense.org
- ansibleguy.net
blocklists:
- example.tor
wildcards:
- example.tor
nxdomain: true
enabled: false
reload: false # speed
register: opn3
failed_when: >
opn3.failed or
not opn3.changed
when: not ansible_check_mode

- name: Nothing changed
ansibleguy.opnsense.unbound_dnsbl:
type:
- atf
whitelists:
- opnsense.org
- ansibleguy.net
blocklists:
- example.tor
wildcards:
- example.tor
nxdomain: true
enabled: false
reload: false # speed
register: opn4
failed_when: >
opn4.failed or
opn4.changed
when: not ansible_check_mode

- name: Cleanup
ansibleguy.opnsense.unbound_dnsbl:
reload: false
when: not ansible_check_mode
Loading