From 497022e13c372ed68afe49aeafca66285e2f8001 Mon Sep 17 00:00:00 2001 From: omnom62 <75066712+omnom62@users.noreply.github.com> Date: Tue, 1 Sep 2026 22:37:23 +1000 Subject: T8323: vyos_nat module (#451) Add NAT module --- plugins/modules/vyos_nat.py | 934 ++++++++++++++++++++++++++++++++++++++++++++ plugins/modules/vyos_vrf.py | 4 +- 2 files changed, 935 insertions(+), 3 deletions(-) create mode 100644 plugins/modules/vyos_nat.py (limited to 'plugins/modules') diff --git a/plugins/modules/vyos_nat.py b/plugins/modules/vyos_nat.py new file mode 100644 index 00000000..7715cb76 --- /dev/null +++ b/plugins/modules/vyos_nat.py @@ -0,0 +1,934 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" +The module file for vyos_nat +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +DOCUMENTATION = """ +module: vyos_nat +version_added: 6.0.0 +short_description: NAT resource module +description: +- This module manages NAT configuration on devices running VyOS. +author: +- Evgeny Molotkov (@omnom62) +notes: +- Tested against VyOS 1.4.3 and 1.5.0. +- This module works with connection C(network_cli). +options: + config: + description: + - The desired configuration for the NAT resource represented as a dictionary. + type: dict + suboptions: + nat: + type: dict + description: Configuration for NAT rules. + suboptions: + cgnat: + type: dict + description: Configuration for Carrier Grade NAT (CGNAT). + suboptions: + log_allocation: + type: bool + description: Log CGNAT address allocations. + pool: + type: dict + description: Configuration for CGNAT pools. + suboptions: + external: + type: list + elements: dict + description: List of external NAT pools for CGNAT. + suboptions: + name: + type: str + required: true + description: Name of the external NAT pool. + external_port_range: + type: str + description: Port range to use for NAT translations in this external pool. + per_user_limit: + type: dict + description: Per-user limit configuration for the external pool. + suboptions: + port: + type: str + description: Maximum number of ports allocated per user. + range: + type: list + elements: dict + description: List of external IP address ranges in the pool. + suboptions: + value: + type: str + required: true + description: IP address, prefix, or range (e.g. 203.0.113.0/24 or 203.0.113.1-203.0.113.60). + seq: + type: str + description: Optional sequence number for this range entry. + internal: + type: list + elements: dict + description: List of internal NAT pools for CGNAT. + suboptions: + name: + type: str + required: true + description: Name of the internal NAT pool. + range: + type: list + elements: str + description: List of internal IP addresses or prefixes in the pool. + rule: + type: list + elements: dict + description: List of CGNAT rules. + suboptions: + id: + type: int + required: true + description: Rule number for CGNAT. + source: + type: dict + description: Source pool configuration for CGNAT translation. + suboptions: + pool: + type: str + description: Source pool name to use for CGNAT translation. + translation: + type: dict + description: Translation pool configuration for CGNAT. + suboptions: + pool: + type: str + description: Translation pool name to use for CGNAT translation. + destination: + type: dict + description: Configuration for destination NAT rules. + suboptions: + rule: + type: list + elements: dict + description: List of destination NAT rules. + suboptions: + id: + type: int + required: true + description: Rule number for destination NAT. + description: + type: str + description: User-friendly description of the destination NAT rule. + protocol: + type: str + description: Protocol to NAT (default all). + packet_type: + type: str + description: Packet type to match. + exclude: + type: bool + description: Exclude packets matching this rule from NAT. + log: + type: bool + description: Log packets hitting this rule. + disable: + type: bool + description: Disable this destination NAT rule. + inbound_interface: + type: dict + description: Match inbound interface. + suboptions: + name: + type: str + description: Interface name to match. + group: + type: str + description: Interface group to match. + destination: + type: dict + description: Match criteria for destination NAT. + suboptions: + address: + type: str + description: IP address, subnet, or range to match. + fqdn: + type: str + description: Fully qualified domain name to match. + port: + type: str + description: Port number or range to match. + address_group: + type: str + description: Address group name to match. + domain_group: + type: str + description: Domain group name to match. + mac_group: + type: str + description: MAC address group name to match. + network_group: + type: str + description: Network group name to match. + port_group: + type: str + description: Port group name to match. + translation: + type: dict + description: Translation configuration for destination NAT. + suboptions: + address: + type: str + description: IP address or prefix to translate destination to. + port: + type: str + description: Port number or range to translate destination port to. + redirect_port: + type: str + description: Redirect to local port number. + address_mapping: + type: str + choices: + - random + - persistent + description: Address mapping mode for translation. + port_mapping: + type: str + choices: + - random + - none + description: Port mapping mode for translation. + load_balance: + type: dict + description: Load balancing configuration for this NAT rule. + suboptions: + hash: + type: list + elements: str + description: Fields to hash on for load balancing. Mutually exclusive with I(translation.address). + choices: + - source-address + - destination-address + - source-port + - destination-port + - random + backend: + type: list + elements: dict + description: List of backends to load-balance across. Weights should sum to 100. + suboptions: + ip: + type: str + description: IP address of the backend translation target. + weight: + type: int + description: Relative weight (1-100) for this backend's share of load-balanced traffic. + source: + type: dict + description: Configuration for source NAT rules. + suboptions: + rule: + type: list + elements: dict + description: List of source NAT rules. + suboptions: + id: + type: int + required: true + description: Rule number for source NAT. + description: + type: str + description: User-friendly description of the source NAT rule. + protocol: + type: str + description: Protocol to NAT (default all). + packet_type: + type: str + description: Packet type to match. + exclude: + type: bool + description: Exclude packets matching this rule from NAT. + log: + type: bool + description: Log packets hitting this rule. + disable: + type: bool + description: Disable this source NAT rule. + outbound_interface: + type: dict + description: Match outbound interface. + suboptions: + name: + type: str + description: Interface name to match. + group: + type: str + description: Interface group to match. + destination: + type: dict + description: Destination match criteria for source NAT. + suboptions: + address: + type: str + description: IP address, subnet, or range to match. + fqdn: + type: str + description: Fully qualified domain name to match. + port: + type: str + description: Port number or range to match. + address_group: + type: str + description: Address group name to match. + domain_group: + type: str + description: Domain group name to match. + mac_group: + type: str + description: MAC address group name to match. + network_group: + type: str + description: Network group name to match. + port_group: + type: str + description: Port group name to match. + source: + type: dict + description: Source match criteria for source NAT. + suboptions: + address: + type: str + description: IP address, subnet, or range to match. + fqdn: + type: str + description: Fully qualified domain name to match. + port: + type: str + description: Port number or range to match. + address_group: + type: str + description: Address group name to match. + domain_group: + type: str + description: Domain group name to match. + mac_group: + type: str + description: MAC address group name to match. + network_group: + type: str + description: Network group name to match. + port_group: + type: str + description: Port group name to match. + translation: + type: dict + description: Translation configuration for source NAT. + suboptions: + address: + type: str + description: IP address or prefix to translate source to. Use masquerade to masquerade as the outbound interface address. + port: + type: str + description: Port number or range to translate source port to. + address_mapping: + type: str + choices: + - random + - persistent + description: Address mapping mode for translation. + port_mapping: + type: str + choices: + - random + - none + description: Port mapping mode for translation. + load_balance: + type: dict + description: Load balancing configuration for this NAT rule. + suboptions: + hash: + type: list + elements: str + description: Fields to hash on for load balancing. Mutually exclusive with I(translation.address). + choices: + - source-address + - destination-address + - source-port + - destination-port + - random + backend: + type: list + elements: dict + description: List of backends to load-balance across. Weights should sum to 100. + suboptions: + ip: + type: str + description: IP address of the backend translation target. + weight: + type: int + description: Relative weight (1-100) for this backend's share of load-balanced traffic. + static: + type: dict + description: Configuration for static one-to-one NAT rules. + suboptions: + rule: + type: list + elements: dict + description: List of static NAT rules. + suboptions: + id: + type: int + required: true + description: Rule number for static NAT. + description: + type: str + description: User-friendly description of the static NAT rule. + destination: + type: dict + description: Match criteria for static NAT. + suboptions: + address: + type: str + description: IP address, subnet, or range to match. + inbound_interface: + type: str + description: Inbound interface that this static NAT rule applies to. + log: + type: bool + description: Log packets hitting this static NAT rule. + translation: + type: dict + description: Translation configuration for static NAT. + suboptions: + address: + type: str + description: IP address or prefix to translate to. + nat64: + type: dict + description: Configuration for NAT64 (IPv6-to-IPv4) rules. + suboptions: + source: + type: dict + description: Configuration for NAT64 source rules. + suboptions: + rule: + type: list + elements: dict + description: List of NAT64 source rules. + suboptions: + id: + type: int + required: true + description: Rule number for NAT64 source rule (1-999999). + description: + type: str + description: User-friendly description of the NAT64 source rule. + disable: + type: bool + description: Disable this NAT64 source rule. + match: + type: dict + description: Match criteria for NAT64 source rule. + suboptions: + mark: + type: int + description: Match on firewall mark value (1-2147483647). + source: + type: dict + description: IPv6 source prefix to match for NAT64 translation. + suboptions: + prefix: + type: str + description: IPv6 source prefix to match (h:h:h:h:h:h:h:h/x). + translation: + type: dict + description: Translation configuration for NAT64 source rule. + suboptions: + pool: + type: list + elements: dict + description: List of translation pools for NAT64. + suboptions: + id: + type: int + required: true + description: Pool number (1-999999). + address: + type: str + description: IPv4 address or prefix for translation pool. + description: + type: str + description: User-friendly description of the translation pool. + disable: + type: bool + description: Disable this translation pool. + port: + type: str + description: Port number or range for translation pool. + protocol: + type: str + choices: + - icmp + - tcp + - udp + description: Protocol for this translation pool entry. + nat66: + type: dict + description: Configuration for NAT66 (IPv6-to-IPv6) rules. + suboptions: + destination: + type: dict + description: Configuration for NAT66 destination rules. + suboptions: + rule: + type: list + elements: dict + description: List of NAT66 destination rules. + suboptions: + id: + type: int + required: true + description: Rule number for NAT66 destination rule. + description: + type: str + description: User-friendly description of the NAT66 destination rule. + destination: + type: dict + description: Match criteria for NAT66 destination rule. + suboptions: + address: + type: str + description: IPv6 address or prefix to match. + port: + type: str + description: Port number or range to match. + disable: + type: bool + description: Disable this NAT66 destination rule. + exclude: + type: bool + description: Exclude packets matching this rule from NAT66. + inbound_interface: + type: dict + description: Inbound interface to match for NAT66 destination rule. + suboptions: + name: + type: str + description: Interface name to match. + log: + type: bool + description: Log packets hitting this NAT66 destination rule. + protocol: + type: str + description: Protocol to match. + source: + type: dict + description: Source match criteria for NAT66 destination rule. + suboptions: + address: + type: str + description: IPv6 source address or prefix to match. + port: + type: str + description: Source port number or range to match. + translation: + type: dict + description: Translation configuration for NAT66 destination rule. + suboptions: + address: + type: str + description: IPv6 address or prefix to translate destination to. + port: + type: str + description: Port number or range to translate destination port to. + source: + type: dict + description: Configuration for NAT66 source rules. + suboptions: + rule: + type: list + elements: dict + description: List of NAT66 source rules. + suboptions: + id: + type: int + required: true + description: Rule number for NAT66 source rule. + description: + type: str + description: User-friendly description of the NAT66 source rule. + destination: + type: dict + description: Destination match criteria for NAT66 source rule. + suboptions: + port: + type: str + description: Destination port number or range to match. + prefix: + type: str + description: IPv6 destination prefix to match (h:h:h:h:h:h:h:h/x). + disable: + type: bool + description: Disable this NAT66 source rule. + exclude: + type: bool + description: Exclude packets matching this rule from NAT66. + log: + type: bool + description: Log packets hitting this NAT66 source rule. + outbound_interface: + type: dict + description: Outbound interface to match for NAT66 source rule. + suboptions: + name: + type: str + description: Interface name to match. + protocol: + type: str + description: Protocol to match. + source: + type: dict + description: Source match criteria for NAT66 source rule. + suboptions: + port: + type: str + description: Source port number or range to match. + prefix: + type: str + description: IPv6 source prefix to match (h:h:h:h:h:h:h:h/x). + translation: + type: dict + description: Translation configuration for NAT66 source rule. + suboptions: + address: + type: str + description: IPv6 address or prefix to translate source to. Use masquerade to masquerade as the outbound interface address. + port: + type: str + description: Port number or range to translate source port to. + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the VyOS device by + executing the command B(show configuration commands | match 'nat'). + - The state I(parsed) reads the configuration from C(show configuration commands | match 'nat') + and transforms it into Ansible structured data as per the module argspec. + The value is then returned in the I(parsed) key within the result. + - The state I(replaced) replaces only the provided configuration, while I(overridden) removes any + existing NAT configuration not specified in I(config). + type: str + state: + description: + - The state the configuration should be left in. + type: str + choices: + - deleted + - merged + - overridden + - replaced + - gathered + - rendered + - parsed + default: merged +""" +EXAMPLES = """ +# Using merged - configure CGNAT +- name: Merge CGNAT configuration + vyos.vyos.vyos_nat: + config: + nat: + cgnat: + log_allocation: true + pool: + external: + - name: ext-pool-1 + external_port_range: "10000-20000" + per_user_limit: + port: "200" + range: + - value: 203.0.113.0/24 + internal: + - name: int-pool-1 + range: + - 10.0.0.0/24 + rule: + - id: 1 + source: + pool: int-pool-1 + translation: + pool: ext-pool-1 + state: merged + +# Using merged - configure destination NAT +- name: Merge destination NAT rule + vyos.vyos.vyos_nat: + config: + nat: + destination: + rule: + - id: 100 + description: "Web server NAT" + protocol: tcp + log: true + destination: + address: 198.51.100.10 + port: "80" + translation: + address: 192.168.1.10 + port: "8080" + state: merged + +# Using merged - configure source NAT +- name: Merge source NAT rule + vyos.vyos.vyos_nat: + config: + nat: + source: + rule: + - id: 200 + description: "Outbound masquerade" + protocol: tcp + log: true + outbound_interface: + name: eth0 + translation: + address: masquerade + state: merged + +# Using merged - configure static NAT +- name: Merge static NAT rule + vyos.vyos.vyos_nat: + config: + nat: + static: + rule: + - id: 300 + description: "Static mapping" + inbound_interface: eth2 + destination: + address: 198.51.100.20 + translation: + address: 192.168.1.20 + log: true + state: merged + +# Using merged - configure NAT64 +- name: Merge NAT64 source rule + vyos.vyos.vyos_nat: + config: + nat64: + source: + rule: + - id: 10 + description: "NAT64 example" + source: + prefix: 2001:db8::/96 + match: + mark: 100 + translation: + pool: + - id: 1 + address: 192.168.100.10 + port: "1-65535" + protocol: udp + state: merged + +# Using merged - configure NAT66 +- name: Merge NAT66 destination rule + vyos.vyos.vyos_nat: + config: + nat66: + destination: + rule: + - id: 20 + description: "NAT66 DNAT" + protocol: tcp + inbound_interface: + name: eth1 + destination: + address: 2001:db8::1 + translation: + address: 2001:db8:1::10 + port: "8443" + state: merged + +# Using replaced - replace specific NAT rules +- name: Replace destination NAT rule + vyos.vyos.vyos_nat: + config: + nat: + destination: + rule: + - id: 100 + description: "Replaced web server NAT" + protocol: tcp + destination: + address: 198.51.100.10 + port: "443" + translation: + address: 192.168.1.10 + port: "8443" + state: replaced + +# Using overridden - override entire NAT configuration +- name: Override entire NAT configuration + vyos.vyos.vyos_nat: + config: + nat: + destination: + rule: + - id: 100 + description: "Only rule after override" + protocol: tcp + destination: + address: 198.51.100.10 + port: "80" + translation: + address: 192.168.1.10 + port: "8080" + state: overridden + +# Using deleted - delete all NAT configuration +- name: Delete all NAT configuration + vyos.vyos.vyos_nat: + state: deleted + +# Using deleted - delete specific NAT rules +- name: Delete specific NAT rules + vyos.vyos.vyos_nat: + config: + nat: + destination: + rule: + - id: 100 + source: + rule: + - id: 200 + nat64: + source: + rule: + - id: 10 + state: deleted + +# Using gathered +- name: Gather NAT configuration from device + vyos.vyos.vyos_nat: + state: gathered + +# Using rendered +- name: Render NAT configuration offline + vyos.vyos.vyos_nat: + config: + nat: + destination: + rule: + - id: 100 + description: "Rendered rule" + protocol: tcp + destination: + address: 198.51.100.10 + port: "80" + translation: + address: 192.168.1.10 + port: "8080" + state: rendered + +# Using parsed +- name: Parse NAT configuration from file + vyos.vyos.vyos_nat: + running_config: "{{ lookup('file', './nat_config.cfg') }}" + state: parsed +""" +RETURN = """ +before: + description: The configuration prior to the module execution. + returned: when I(state) is C(merged), C(replaced), C(overridden) or C(deleted) + type: dict + sample: > + This output will always be in the same format as the + module argspec. +after: + description: The resulting configuration after module execution. + returned: when changed + type: dict + sample: > + This output will always be in the same format as the + module argspec. +commands: + description: The set of commands pushed to the remote device. + returned: when I(state) is C(merged), C(replaced), C(overridden) or C(deleted) + type: list + sample: + - set nat destination rule 100 description 'Web server NAT' + - set nat destination rule 100 protocol tcp + - set nat destination rule 100 inbound-interface name eth2 + - set nat destination rule 100 destination address 198.51.100.10 + - set nat destination rule 100 translation address 192.168.1.10 + - delete nat source rule 200 +rendered: + description: The provided configuration in the task rendered in device-native format (offline). + returned: when I(state) is C(rendered) + type: list + sample: + - set nat destination rule 100 description 'Web server NAT' + - set nat destination rule 100 protocol tcp + - set nat destination rule 100 inbound-interface name eth2 + - set nat destination rule 100 destination address 198.51.100.10 + - set nat destination rule 100 translation address 192.168.1.10 +gathered: + description: Facts about the network resource gathered from the remote device as structured data. + returned: when I(state) is C(gathered) + type: dict + sample: > + This output will always be in the same format as the + module argspec. +parsed: + description: The device native config provided in I(running_config) option parsed into structured data as per module argspec. + returned: when I(state) is C(parsed) + type: dict + sample: > + This output will always be in the same format as the + module argspec. +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.nat.nat import ( + NatArgs, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.nat.nat import ( + Nat, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule( + argument_spec=NatArgs.argument_spec, + mutually_exclusive=[["config", "running_config"]], + required_if=[ + ["state", "merged", ["config"]], + ["state", "replaced", ["config"]], + ["state", "overridden", ["config"]], + ["state", "rendered", ["config"]], + ["state", "parsed", ["running_config"]], + ], + supports_check_mode=True, + ) + + result = Nat(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/vyos_vrf.py b/plugins/modules/vyos_vrf.py index defe24bb..3f7ae10e 100644 --- a/plugins/modules/vyos_vrf.py +++ b/plugins/modules/vyos_vrf.py @@ -1,8 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright 2024 Red Hat -# GNU General Public License v3.0+ -# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ The module file for vyos_vrf -- cgit v1.2.3