Skip to content

Commit

Permalink
Merge pull request #146 from scsitteam/one_to_one
Browse files Browse the repository at this point in the history
Implement one_to_one NAT/BINAT module
  • Loading branch information
ansibleguy authored Feb 7, 2025
2 parents 140c7c5 + be45846 commit d4560ab
Show file tree
Hide file tree
Showing 16 changed files with 483 additions and 24 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ not implemented => development => [testing](https://github.com/ansibleguy/collec
| **DNS** | ansibleguy.opnsense.unbound_dot | [Docs](https://opnsense.ansibleguy.net/modules/unbound_dot.html) | stable |
| **DNS** | ansibleguy.opnsense.unbound_host | [Docs](https://opnsense.ansibleguy.net/modules/unbound_host.html) | stable |
| **DNS** | ansibleguy.opnsense.unbound_host_alias | [Docs](https://opnsense.ansibleguy.net/modules/unbound_host_alias.html) | stable |
| **DNS** | ansibleguy.opnsense.unbound_dnsbl | [Docs](https://opnsense.ansibleguy.net/modules/unbound_host_alias.html) | unstable || **Syslog** | ansibleguy.opnsense.syslog | [Docs](https://opnsense.ansibleguy.net/modules/syslog.html) | stable |
| **DNS** | ansibleguy.opnsense.unbound_dnsbl | [Docs](https://opnsense.ansibleguy.net/modules/unbound_host_alias.html) | unstable |
| **Syslog** | ansibleguy.opnsense.syslog | [Docs](https://opnsense.ansibleguy.net/modules/syslog.html) | stable |
| **IPSec** | ansibleguy.opnsense.ipsec_connection, ansibleguy.opnsense.ipsec_tunnel | [Docs](https://opnsense.ansibleguy.net/modules/ipsec.html) | stable |
| **IPSec** | ansibleguy.opnsense.ipsec_pool, ansibleguy.opnsense.ipsec_network | [Docs](https://opnsense.ansibleguy.net/modules/ipsec.html) | stable |
| **IPSec** | ansibleguy.opnsense.ipsec_auth_local | [Docs](https://opnsense.ansibleguy.net/modules/ipsec.html) | stable |
Expand All @@ -138,7 +139,8 @@ not implemented => development => [testing](https://github.com/ansibleguy/collec
| **Interfaces** | ansibleguy.opnsense.interface_vip | [Docs](https://opnsense.ansibleguy.net/modules/interface.html) | stable |
| **Interfaces** | ansibleguy.opnsense.interface_lagg | [Docs](https://opnsense.ansibleguy.net/modules/interface.html) | unstable |
| **Interfaces** | ansibleguy.opnsense.interface_loopback | [Docs](https://opnsense.ansibleguy.net/modules/interface.html) | unstable |
| **NAT** | ansibleguy.opnsense.source_nat, ansibleguy.opnsense.snat | [Docs](https://opnsense.ansibleguy.net/modules/source_nat.html) | stable |
| **NAT** | ansibleguy.opnsense.nat_source | [Docs](https://opnsense.ansibleguy.net/modules/source_nat.html) | stable |
| **NAT** | ansibleguy.opnsense.nat_one_to_one | [Docs](https://opnsense.ansibleguy.net/modules/one_to_one.html) | unstable |
| **Dynamic Routing** | ansibleguy.opnsense.frr_diagnostic | [Docs](https://opnsense.ansibleguy.net/modules/frr_diagnostic.html) | stable |
| **Dynamic Routing** | ansibleguy.opnsense.frr_general | [Docs](https://opnsense.ansibleguy.net/modules/frr_general.html) | stable |
| **Dynamic Routing** | ansibleguy.opnsense.frr_bfd_general | [Docs](https://opnsense.ansibleguy.net/modules/frr_bfd.html#ansibleguy-opnsense-frr-bfd-general) | stable |
Expand Down
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_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', , 'interface_gre', 'postfix_general', 'postfix_domain', 'postfix_recipient', 'postfix_recipientbcc', 'postfix_sender', 'postfix_senderbcc', 'postfix_sendercanonical', 'postfix_headercheck', 'postfix_address'"
"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_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', 'nat_source', 'nat_one_to_one', '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', 'interface_gre', 'postfix_general', 'postfix_domain', 'postfix_recipient', 'postfix_recipientbcc', 'postfix_sender', 'postfix_senderbcc', 'postfix_sendercanonical', 'postfix_headercheck', 'postfix_address'"

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

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

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

==============
NAT One-To-One
==============

**STATE**: unstable

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

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

**Service Docs**: `one_to_one <https://docs.opnsense.org/manual/nat.html#one-to-one>`_

Contribution
************

Thanks to `@jiuka <https://github.com/jiuka>`_ for developing this module!

----

Info
****

Savepoint
=========

You can prevent lockout-situations using the savepoint systems:

- :ref:`ansibleguy.opnsense.savepoint <modules_savepoint>`


Definition
**********

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

"match_fields","list","false","['interface', 'external'']","\-","Fields that are used to match configured rules with the running config - if any of those fields are changed, the module will think it's a new rule. At least one of: 'sequence', 'action', 'interface', 'direction', 'ip_protocol', 'protocol', 'source_invert', 'source_net', 'source_port', 'destination_invert', 'destination_net', 'destination_port', 'gateway', 'description', 'uuid'"
"sequence","int","false","1","seq","Sequence for rule processing, Integer between 1 and 1000000"
"log","boolean","false","true","l","If rule matches should be shown in the firewall logs"
"interface","string","false for deletion, else true","\-","i, int","The interface to match this rule on"
"type","string","false","binnat","\-","NAT type to use. ONE of: binat or nat. See `Some terms explained <https://docs.opnsense.org/manual/nat.html#some-terms-explained>`_."
"external","string","false for deletion, else true","\-","external_net, ext, e","External subnet's starting address for the 1:1 mapping or network. This is the address or network the traffic will translate to/from."
"source_net","string","false for deletion, else true","\-","s, src, source","Internal subnet for the 1:1 mapping."
"source_invert","boolean","false","false","si, src_inv, src_not","Inverted matching of the source."
"destination_net","string","false","'any'","d, dest, destination","The 1:1 mapping will only be used for connections to or from the specified destination. Hint: this is usually 'any'."
"destination_invert","boolean","false","false","di, dest_inv, dest_not","Inverted matching of the destination"
"nat_reflection","string","false","''","\-","One of: '', enable, disable. See `Some terms explained <https://docs.opnsense.org/manual/nat.html#some-terms-explained>`_."
"description","string","false","\-","desc","Description for the rule"
"state","string","false","'present'","st","State of the rule. One of: 'present', 'absent'"
"enabled","boolean","false","true","en","If the rule should be en- or disabled"
"uuid","string","false","\-","\-","Optionally you can supply the uuid of an existing rule"
"reload","boolean","false","true","apply", .. include:: ../_include/param_reload.rst

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

----

Usage
*****

To add One-to-One NAT rules - see: `OPNSense Documentation <https://docs.opnsense.org/manual/nat.html#one-to-one>`_

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: 'nat_one_to_one'
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.nat_one_to_one:
#sequence: 1
interface: 'lan'
#type: binnat
external: '8.8.8.8'
source_net: '192.168.0.1'
#source_invert: false
#destination_net: 'any'
#destination_invert: false
#nat_reflection: ''
description: 'Map External IP 8.8.8.8 to Internal 192.168.0.1'
# enabled: true
# state: 'absent'
# debug: false
- name: Listing jobs
ansibleguy.opnsense.list:
# target: 'nat_one_to_one'
register: existing_one_to_one
- name: Printing
ansible.builtin.debug:
var: existing_one_to_one.data
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
.. _modules_source_nat:
.. _modules_nat_source:

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

==========
Source NAT
NAT Source
==========

**STATE**: stable

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

**API Docs**: `Core - Firewall <https://docs.opnsense.org/development/api/core/firewall.html>`_

Expand All @@ -32,6 +32,7 @@ This plugin has some limitations you need to know of:
* per example see menu: 'Interface - Assignments - Interface ID (in brackets)'
* this brings problems if the interface-names are not the same on both nodes when using HA-setups

----

Info
****
Expand Down Expand Up @@ -81,6 +82,8 @@ Module alias: ansibleguy.opnsense.snat

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

----

Usage
*****

Expand All @@ -107,15 +110,15 @@ Examples
firewall: 'opnsense.template.ansibleguy.net'
api_credential_file: '/home/guy/.secret/opn.key'
ansibleguy.opnsense.source_nat:
ansibleguy.opnsense.nat_source:
match_fields: ['description']
ansibleguy.opnsense.list:
target: 'source_nat'
target: 'nat_source'
tasks:
- name: Example
ansibleguy.opnsense.source_nat:
ansibleguy.opnsense.nat_source:
description: 'example'
match_fields: ['description']
target: '192.168.0.1'
Expand All @@ -139,7 +142,7 @@ Examples
# reload: true
- name: Adding rule
ansibleguy.opnsense.source_nat:
ansibleguy.opnsense.nat_source:
description: 'test1'
source: '192.168.0.0/24'
destination: '10.0.0.0/24'
Expand All @@ -148,7 +151,7 @@ Examples
# match_fields: ['description']
- name: Disabling rule
ansibleguy.opnsense.source_nat:
ansibleguy.opnsense.nat_source:
description: 'test1'
source: '192.168.0.0/24'
destination: '10.0.0.0/24'
Expand All @@ -159,15 +162,15 @@ Examples
- name: Listing
ansibleguy.opnsense.list:
# target: 'source_nat'
# target: 'nat_source'
register: existing_entries
- name: Printing peers
ansible.builtin.debug:
var: existing_entries.data
- name: Removing rule
ansibleguy.opnsense.source_nat:
ansibleguy.opnsense.nat_source:
description: 'test1'
state: 'absent'
# match_fields: ['description']
5 changes: 3 additions & 2 deletions docs/source/modules/savepoint.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ Here is the basic process:
It currently just works with the 'Firewall' plugin:

- :ref:`ansibleguy.opnsense.rule <modules_rule>`
- :ref:`ansibleguy.opnsense.source_nat <modules_source_nat>`
- :ref:`ansibleguy.opnsense.nat_source <modules_nat_source>`
- :ref:`ansibleguy.opnsense.nat_one_to_one <modules_nat_one_to_one>`

Definition
**********
Expand All @@ -38,7 +39,7 @@ Definition

"name","string","false","'create'","Action to execute. One of: 'create', 'revert', 'apply', 'cancel_rollback', 'cancel'"
"revision","string","false, true if action is one of 'apply', 'revert' or 'cancel_rollback'","\-","Savepoint revision to apply, revert or cancel_rollback"
"controller","string","false","'filter'","Controller to manage the savepoint of. One of: 'source_nat', 'filter'"
"controller","string","false","'filter'","Controller to manage the savepoint of. One of: 'source_nat', 'filter', 'one_to_one'"
"api_module","string","false","'firewall'","Module to manage the savepoint of. Currently only supports 'firewall'"

.. include:: ../_include/param_basic.rst
Expand Down
7 changes: 6 additions & 1 deletion meta/runtime.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ action_groups:
- ansibleguy.opnsense.route
- ansibleguy.opnsense.gateway
nat:
- ansibleguy.opnsense.source_nat
- ansibleguy.opnsense.nat_source
- ansibleguy.opnsense.nat_one_to_one
system:
- ansibleguy.opnsense.list
- ansibleguy.opnsense.reload
Expand Down Expand Up @@ -222,3 +223,7 @@ plugin_routing:
redirect: ansibleguy.opnsense.acme_validation
acme_automation:
redirect: ansibleguy.opnsense.acme_action
source_nat:
redirect: ansibleguy.opnsense.nat_source
one_to_one:
redirect: ansibleguy.opnsense.nat_one_to_one
63 changes: 63 additions & 0 deletions plugins/module_utils/main/nat_one_to_one.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
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.helper.main import \
validate_int_fields, is_unset
from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.cls import BaseModule


class OneToOne(BaseModule):
FIELD_ID = 'name'
CMDS = {
'add': 'addRule',
'del': 'delRule',
'set': 'setRule',
'search': 'get',
'toggle': 'toggleRule',
}
API_KEY_PATH = 'filter.onetoone.rule'
API_MOD = 'firewall'
API_CONT = 'one_to_one'
FIELDS_CHANGE = [
'log', 'sequence', 'interface', 'type', 'source_net', 'source_invert', 'destination_net', 'destination_invert',
'external', 'nat_reflection', 'description'

]
FIELDS_ALL = ['enabled']
FIELDS_ALL.extend(FIELDS_CHANGE)
FIELDS_TRANSLATE = {
'source_invert': 'source_not',
'destination_invert': 'destination_not',
'nat_reflection': 'natreflection',
}
FIELDS_TYPING = {
'bool': ['enabled', 'log', 'source_invert', 'destination_invert'],
'list': [],
'select': ['interface', 'type', 'nat_reflection'],
'int': [],
}
INT_VALIDATIONS = {
'sequence': {'min': 1, 'max': 99999},
}
EXIST_ATTR = 'rule'
API_CMD_REL = 'apply'

def __init__(self, module: AnsibleModule, result: dict, session: Session = None):
BaseModule.__init__(self=self, m=module, r=result, s=session)
self.rule = {}
self.existing_additionalstuff = None

def check(self) -> None:
if self.p['state'] == 'present':
if is_unset(self.p['interface']):
self.m.fail_json(
"You need to provide an 'interface' to create a one-to-one"
)

validate_int_fields(module=self.m, data=self.p, field_minmax=self.INT_VALIDATIONS)

self.b.find(match_fields=self.p['match_fields'])

if self.p['state'] == 'present':
self.r['diff']['after'] = self.b.build_diff(data=self.p)
File renamed without changes.
10 changes: 7 additions & 3 deletions plugins/modules/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
'interface_lagg', 'interface_loopback', 'unbound_dnsbl', 'dhcp_reservation', 'acme_general', 'acme_account',
'acme_validation', 'acme_action', 'acme_certificate', 'postfix_general', 'postfix_domain', 'postfix_recipient',
'postfix_recipientbcc', 'postfix_sender', 'postfix_senderbcc', 'postfix_sendercanonical', 'postfix_headercheck',
'postfix_address', 'dhcp_subnet', 'dhcp_general', 'interface_gre',
'postfix_address', 'dhcp_subnet', 'dhcp_general', 'interface_gre', 'nat_one_to_one', 'nat_source',
]


Expand Down Expand Up @@ -190,10 +190,14 @@ def run_module():
from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.interface_gre import \
Gre as Target_Obj

elif target == 'source_nat':
from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.source_nat import \
elif target in ['source_nat', 'nat_source']:
from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.nat_source import \
SNat as Target_Obj

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

elif target == 'frr_general':
from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.frr_general \
import General as Target_Obj
Expand Down
Loading

0 comments on commit d4560ab

Please sign in to comment.